| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package android.transition; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.AnimatorSet; |
| import android.animation.ObjectAnimator; |
| import android.animation.RectEvaluator; |
| import android.animation.ValueAnimator; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.graphics.Bitmap; |
| import android.graphics.Canvas; |
| import android.graphics.Rect; |
| import android.graphics.drawable.BitmapDrawable; |
| import android.util.Log; |
| import android.view.SurfaceView; |
| import android.view.TextureView; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.ViewOverlay; |
| |
| import java.util.Map; |
| |
| /** |
| * This transition captures bitmap representations of target views before and |
| * after the scene change and fades between them. |
| * |
| * <p>Note: This transition is not compatible with {@link TextureView} |
| * or {@link SurfaceView}.</p> |
| * |
| * @hide |
| */ |
| public class Crossfade extends Transition { |
| // TODO: Add a hook that lets a Transition call user code to query whether it should run on |
| // a given target view. This would save bitmap comparisons in this transition, for example. |
| |
| private static final String LOG_TAG = "Crossfade"; |
| |
| private static final String PROPNAME_BITMAP = "android:crossfade:bitmap"; |
| private static final String PROPNAME_DRAWABLE = "android:crossfade:drawable"; |
| private static final String PROPNAME_BOUNDS = "android:crossfade:bounds"; |
| |
| private static RectEvaluator sRectEvaluator = new RectEvaluator(); |
| |
| private int mFadeBehavior = FADE_BEHAVIOR_REVEAL; |
| private int mResizeBehavior = RESIZE_BEHAVIOR_SCALE; |
| |
| /** |
| * Flag specifying that the fading animation should cross-fade |
| * between the old and new representation of all affected target |
| * views. This means that the old representation will fade out |
| * while the new one fades in. This effect may work well on views |
| * without solid backgrounds, such as TextViews. |
| * |
| * @see #setFadeBehavior(int) |
| */ |
| public static final int FADE_BEHAVIOR_CROSSFADE = 0; |
| /** |
| * Flag specifying that the fading animation should reveal the |
| * new representation of all affected target views. This means |
| * that the old representation will fade out, gradually |
| * revealing the new representation, which remains opaque |
| * the whole time. This effect may work well on views |
| * with solid backgrounds, such as ImageViews. |
| * |
| * @see #setFadeBehavior(int) |
| */ |
| public static final int FADE_BEHAVIOR_REVEAL = 1; |
| /** |
| * Flag specifying that the fading animation should first fade |
| * out the original representation completely and then fade in the |
| * new one. This effect may be more suitable than the other |
| * fade behaviors for views with. |
| * |
| * @see #setFadeBehavior(int) |
| */ |
| public static final int FADE_BEHAVIOR_OUT_IN = 2; |
| |
| /** |
| * Flag specifying that the transition should not animate any |
| * changes in size between the old and new target views. |
| * This means that no scaling will take place as a result of |
| * this transition |
| * |
| * @see #setResizeBehavior(int) |
| */ |
| public static final int RESIZE_BEHAVIOR_NONE = 0; |
| /** |
| * Flag specifying that the transition should animate any |
| * changes in size between the old and new target views. |
| * This means that the animation will scale the start/end |
| * representations of affected views from the starting size |
| * to the ending size over the course of the animation. |
| * This effect may work well on images, but is not recommended |
| * for text. |
| * |
| * @see #setResizeBehavior(int) |
| */ |
| public static final int RESIZE_BEHAVIOR_SCALE = 1; |
| |
| // TODO: Add fade/resize behaviors to xml resources |
| |
| /** |
| * Sets the type of fading animation that will be run, one of |
| * {@link #FADE_BEHAVIOR_CROSSFADE} and {@link #FADE_BEHAVIOR_REVEAL}. |
| * |
| * @param fadeBehavior The type of fading animation to use when this |
| * transition is run. |
| */ |
| public Crossfade setFadeBehavior(int fadeBehavior) { |
| if (fadeBehavior >= FADE_BEHAVIOR_CROSSFADE && fadeBehavior <= FADE_BEHAVIOR_OUT_IN) { |
| mFadeBehavior = fadeBehavior; |
| } |
| return this; |
| } |
| |
| /** |
| * Returns the fading behavior of the animation. |
| * |
| * @return This crossfade object. |
| * @see #setFadeBehavior(int) |
| */ |
| public int getFadeBehavior() { |
| return mFadeBehavior; |
| } |
| |
| /** |
| * Sets the type of resizing behavior that will be used during the |
| * transition animation, one of {@link #RESIZE_BEHAVIOR_NONE} and |
| * {@link #RESIZE_BEHAVIOR_SCALE}. |
| * |
| * @param resizeBehavior The type of resizing behavior to use when this |
| * transition is run. |
| */ |
| public Crossfade setResizeBehavior(int resizeBehavior) { |
| if (resizeBehavior >= RESIZE_BEHAVIOR_NONE && resizeBehavior <= RESIZE_BEHAVIOR_SCALE) { |
| mResizeBehavior = resizeBehavior; |
| } |
| return this; |
| } |
| |
| /** |
| * Returns the resizing behavior of the animation. |
| * |
| * @return This crossfade object. |
| * @see #setResizeBehavior(int) |
| */ |
| public int getResizeBehavior() { |
| return mResizeBehavior; |
| } |
| |
| @Nullable |
| @Override |
| public Animator createAnimator(@NonNull ViewGroup sceneRoot, |
| @Nullable TransitionValues startValues, |
| @Nullable TransitionValues endValues) { |
| if (startValues == null || endValues == null) { |
| return null; |
| } |
| final boolean useParentOverlay = mFadeBehavior != FADE_BEHAVIOR_REVEAL; |
| final View view = endValues.view; |
| Map<String, Object> startVals = startValues.values; |
| Map<String, Object> endVals = endValues.values; |
| Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS); |
| Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS); |
| Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP); |
| Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP); |
| final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE); |
| final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE); |
| if (Transition.DBG) { |
| Log.d(LOG_TAG, "StartBitmap.sameAs(endBitmap) = " + startBitmap.sameAs(endBitmap) + |
| " for start, end: " + startBitmap + ", " + endBitmap); |
| } |
| if (startDrawable != null && endDrawable != null && !startBitmap.sameAs(endBitmap)) { |
| ViewOverlay overlay = useParentOverlay ? |
| ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); |
| if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { |
| overlay.add(endDrawable); |
| } |
| overlay.add(startDrawable); |
| // The transition works by placing the end drawable under the start drawable and |
| // gradually fading out the start drawable. So it's not really a cross-fade, but rather |
| // a reveal of the end scene over time. Also, animate the bounds of both drawables |
| // to mimic the change in the size of the view itself between scenes. |
| ObjectAnimator anim; |
| if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { |
| // Fade out completely halfway through the transition |
| anim = ObjectAnimator.ofInt(startDrawable, "alpha", 255, 0, 0); |
| } else { |
| anim = ObjectAnimator.ofInt(startDrawable, "alpha", 0); |
| } |
| anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { |
| @Override |
| public void onAnimationUpdate(ValueAnimator animation) { |
| // TODO: some way to auto-invalidate views based on drawable changes? callbacks? |
| view.invalidate(startDrawable.getBounds()); |
| } |
| }); |
| ObjectAnimator anim1 = null; |
| if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) { |
| // start fading in halfway through the transition |
| anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 0, 1); |
| } else if (mFadeBehavior == FADE_BEHAVIOR_CROSSFADE) { |
| anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1); |
| } |
| if (Transition.DBG) { |
| Log.d(LOG_TAG, "Crossfade: created anim " + anim + " for start, end values " + |
| startValues + ", " + endValues); |
| } |
| anim.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| ViewOverlay overlay = useParentOverlay ? |
| ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay(); |
| overlay.remove(startDrawable); |
| if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) { |
| overlay.remove(endDrawable); |
| } |
| } |
| }); |
| AnimatorSet set = new AnimatorSet(); |
| set.playTogether(anim); |
| if (anim1 != null) { |
| set.playTogether(anim1); |
| } |
| if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE && !startBounds.equals(endBounds)) { |
| if (Transition.DBG) { |
| Log.d(LOG_TAG, "animating from startBounds to endBounds: " + |
| startBounds + ", " + endBounds); |
| } |
| Animator anim2 = ObjectAnimator.ofObject(startDrawable, "bounds", |
| sRectEvaluator, startBounds, endBounds); |
| set.playTogether(anim2); |
| if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE) { |
| // TODO: How to handle resizing with a CROSSFADE (vs. REVEAL) effect |
| // when we are animating the view directly? |
| Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds", |
| sRectEvaluator, startBounds, endBounds); |
| set.playTogether(anim3); |
| } |
| } |
| return set; |
| } else { |
| return null; |
| } |
| } |
| |
| private void captureValues(TransitionValues transitionValues) { |
| View view = transitionValues.view; |
| Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight()); |
| if (mFadeBehavior != FADE_BEHAVIOR_REVEAL) { |
| bounds.offset(view.getLeft(), view.getTop()); |
| } |
| transitionValues.values.put(PROPNAME_BOUNDS, bounds); |
| |
| if (Transition.DBG) { |
| Log.d(LOG_TAG, "Captured bounds " + transitionValues.values.get(PROPNAME_BOUNDS)); |
| } |
| Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), |
| Bitmap.Config.ARGB_8888); |
| if (view instanceof TextureView) { |
| bitmap = ((TextureView) view).getBitmap(); |
| } else { |
| Canvas c = new Canvas(bitmap); |
| view.draw(c); |
| } |
| transitionValues.values.put(PROPNAME_BITMAP, bitmap); |
| // TODO: I don't have resources, can't call the non-deprecated method? |
| BitmapDrawable drawable = new BitmapDrawable(bitmap); |
| // TODO: lrtb will be wrong if the view has transXY set |
| drawable.setBounds(bounds); |
| transitionValues.values.put(PROPNAME_DRAWABLE, drawable); |
| } |
| |
| @Override |
| public void captureStartValues(TransitionValues transitionValues) { |
| captureValues(transitionValues); |
| } |
| |
| @Override |
| public void captureEndValues(TransitionValues transitionValues) { |
| captureValues(transitionValues); |
| } |
| } |