Home | History | Annotate | Download | only in transition
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.transition;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.AnimatorSet;
     22 import android.animation.ObjectAnimator;
     23 import android.animation.RectEvaluator;
     24 import android.animation.ValueAnimator;
     25 import android.graphics.Bitmap;
     26 import android.graphics.Canvas;
     27 import android.graphics.Rect;
     28 import android.graphics.drawable.BitmapDrawable;
     29 import android.util.Log;
     30 import android.view.SurfaceView;
     31 import android.view.TextureView;
     32 import android.view.View;
     33 import android.view.ViewGroup;
     34 import android.view.ViewOverlay;
     35 
     36 import java.util.Map;
     37 
     38 /**
     39  * This transition captures bitmap representations of target views before and
     40  * after the scene change and fades between them.
     41  *
     42  * <p>Note: This transition is not compatible with {@link TextureView}
     43  * or {@link SurfaceView}.</p>
     44  *
     45  * @hide
     46  */
     47 public class Crossfade extends Transition {
     48     // TODO: Add a hook that lets a Transition call user code to query whether it should run on
     49     // a given target view. This would save bitmap comparisons in this transition, for example.
     50 
     51     private static final String LOG_TAG = "Crossfade";
     52 
     53     private static final String PROPNAME_BITMAP = "android:crossfade:bitmap";
     54     private static final String PROPNAME_DRAWABLE = "android:crossfade:drawable";
     55     private static final String PROPNAME_BOUNDS = "android:crossfade:bounds";
     56 
     57     private static RectEvaluator sRectEvaluator = new RectEvaluator();
     58 
     59     private int mFadeBehavior = FADE_BEHAVIOR_REVEAL;
     60     private int mResizeBehavior = RESIZE_BEHAVIOR_SCALE;
     61 
     62     /**
     63      * Flag specifying that the fading animation should cross-fade
     64      * between the old and new representation of all affected target
     65      * views. This means that the old representation will fade out
     66      * while the new one fades in. This effect may work well on views
     67      * without solid backgrounds, such as TextViews.
     68      *
     69      * @see #setFadeBehavior(int)
     70      */
     71     public static final int FADE_BEHAVIOR_CROSSFADE = 0;
     72     /**
     73      * Flag specifying that the fading animation should reveal the
     74      * new representation of all affected target views. This means
     75      * that the old representation will fade out, gradually
     76      * revealing the new representation, which remains opaque
     77      * the whole time. This effect may work well on views
     78      * with solid backgrounds, such as ImageViews.
     79      *
     80      * @see #setFadeBehavior(int)
     81      */
     82     public static final int FADE_BEHAVIOR_REVEAL = 1;
     83     /**
     84      * Flag specifying that the fading animation should first fade
     85      * out the original representation completely and then fade in the
     86      * new one. This effect may be more suitable than the other
     87      * fade behaviors for views with.
     88      *
     89      * @see #setFadeBehavior(int)
     90      */
     91     public static final int FADE_BEHAVIOR_OUT_IN = 2;
     92 
     93     /**
     94      * Flag specifying that the transition should not animate any
     95      * changes in size between the old and new target views.
     96      * This means that no scaling will take place as a result of
     97      * this transition
     98      *
     99      * @see #setResizeBehavior(int)
    100      */
    101     public static final int RESIZE_BEHAVIOR_NONE = 0;
    102     /**
    103      * Flag specifying that the transition should animate any
    104      * changes in size between the old and new target views.
    105      * This means that the animation will scale the start/end
    106      * representations of affected views from the starting size
    107      * to the ending size over the course of the animation.
    108      * This effect may work well on images, but is not recommended
    109      * for text.
    110      *
    111      * @see #setResizeBehavior(int)
    112      */
    113     public static final int RESIZE_BEHAVIOR_SCALE = 1;
    114 
    115     // TODO: Add fade/resize behaviors to xml resources
    116 
    117     /**
    118      * Sets the type of fading animation that will be run, one of
    119      * {@link #FADE_BEHAVIOR_CROSSFADE} and {@link #FADE_BEHAVIOR_REVEAL}.
    120      *
    121      * @param fadeBehavior The type of fading animation to use when this
    122      * transition is run.
    123      */
    124     public Crossfade setFadeBehavior(int fadeBehavior) {
    125         if (fadeBehavior >= FADE_BEHAVIOR_CROSSFADE && fadeBehavior <= FADE_BEHAVIOR_OUT_IN) {
    126             mFadeBehavior = fadeBehavior;
    127         }
    128         return this;
    129     }
    130 
    131     /**
    132      * Returns the fading behavior of the animation.
    133      *
    134      * @return This crossfade object.
    135      * @see #setFadeBehavior(int)
    136      */
    137     public int getFadeBehavior() {
    138         return mFadeBehavior;
    139     }
    140 
    141     /**
    142      * Sets the type of resizing behavior that will be used during the
    143      * transition animation, one of {@link #RESIZE_BEHAVIOR_NONE} and
    144      * {@link #RESIZE_BEHAVIOR_SCALE}.
    145      *
    146      * @param resizeBehavior The type of resizing behavior to use when this
    147      * transition is run.
    148      */
    149     public Crossfade setResizeBehavior(int resizeBehavior) {
    150         if (resizeBehavior >= RESIZE_BEHAVIOR_NONE && resizeBehavior <= RESIZE_BEHAVIOR_SCALE) {
    151             mResizeBehavior = resizeBehavior;
    152         }
    153         return this;
    154     }
    155 
    156     /**
    157      * Returns the resizing behavior of the animation.
    158      *
    159      * @return This crossfade object.
    160      * @see #setResizeBehavior(int)
    161      */
    162     public int getResizeBehavior() {
    163         return mResizeBehavior;
    164     }
    165 
    166     @Override
    167     public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
    168             TransitionValues endValues) {
    169         if (startValues == null || endValues == null) {
    170             return null;
    171         }
    172         final boolean useParentOverlay = mFadeBehavior != FADE_BEHAVIOR_REVEAL;
    173         final View view = endValues.view;
    174         Map<String, Object> startVals = startValues.values;
    175         Map<String, Object> endVals = endValues.values;
    176         Rect startBounds = (Rect) startVals.get(PROPNAME_BOUNDS);
    177         Rect endBounds = (Rect) endVals.get(PROPNAME_BOUNDS);
    178         Bitmap startBitmap = (Bitmap) startVals.get(PROPNAME_BITMAP);
    179         Bitmap endBitmap = (Bitmap) endVals.get(PROPNAME_BITMAP);
    180         final BitmapDrawable startDrawable = (BitmapDrawable) startVals.get(PROPNAME_DRAWABLE);
    181         final BitmapDrawable endDrawable = (BitmapDrawable) endVals.get(PROPNAME_DRAWABLE);
    182         if (Transition.DBG) {
    183             Log.d(LOG_TAG, "StartBitmap.sameAs(endBitmap) = " + startBitmap.sameAs(endBitmap) +
    184                     " for start, end: " + startBitmap + ", " + endBitmap);
    185         }
    186         if (startDrawable != null && endDrawable != null && !startBitmap.sameAs(endBitmap)) {
    187             ViewOverlay overlay = useParentOverlay ?
    188                     ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay();
    189             if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) {
    190                 overlay.add(endDrawable);
    191             }
    192             overlay.add(startDrawable);
    193             // The transition works by placing the end drawable under the start drawable and
    194             // gradually fading out the start drawable. So it's not really a cross-fade, but rather
    195             // a reveal of the end scene over time. Also, animate the bounds of both drawables
    196             // to mimic the change in the size of the view itself between scenes.
    197             ObjectAnimator anim;
    198             if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) {
    199                 // Fade out completely halfway through the transition
    200                 anim = ObjectAnimator.ofInt(startDrawable, "alpha", 255, 0, 0);
    201             } else {
    202                 anim = ObjectAnimator.ofInt(startDrawable, "alpha", 0);
    203             }
    204             anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    205                 @Override
    206                 public void onAnimationUpdate(ValueAnimator animation) {
    207                     // TODO: some way to auto-invalidate views based on drawable changes? callbacks?
    208                     view.invalidate(startDrawable.getBounds());
    209                 }
    210             });
    211             ObjectAnimator anim1 = null;
    212             if (mFadeBehavior == FADE_BEHAVIOR_OUT_IN) {
    213                 // start fading in halfway through the transition
    214                 anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 0, 1);
    215             } else if (mFadeBehavior == FADE_BEHAVIOR_CROSSFADE) {
    216                 anim1 = ObjectAnimator.ofFloat(view, View.ALPHA, 0, 1);
    217             }
    218             if (Transition.DBG) {
    219                 Log.d(LOG_TAG, "Crossfade: created anim " + anim + " for start, end values " +
    220                         startValues + ", " + endValues);
    221             }
    222             anim.addListener(new AnimatorListenerAdapter() {
    223                 @Override
    224                 public void onAnimationEnd(Animator animation) {
    225                     ViewOverlay overlay = useParentOverlay ?
    226                             ((ViewGroup) view.getParent()).getOverlay() : view.getOverlay();
    227                     overlay.remove(startDrawable);
    228                     if (mFadeBehavior == FADE_BEHAVIOR_REVEAL) {
    229                         overlay.remove(endDrawable);
    230                     }
    231                 }
    232             });
    233             AnimatorSet set = new AnimatorSet();
    234             set.playTogether(anim);
    235             if (anim1 != null) {
    236                 set.playTogether(anim1);
    237             }
    238             if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE && !startBounds.equals(endBounds)) {
    239                 if (Transition.DBG) {
    240                     Log.d(LOG_TAG, "animating from startBounds to endBounds: " +
    241                             startBounds + ", " + endBounds);
    242                 }
    243                 Animator anim2 = ObjectAnimator.ofObject(startDrawable, "bounds",
    244                         sRectEvaluator, startBounds, endBounds);
    245                 set.playTogether(anim2);
    246                 if (mResizeBehavior == RESIZE_BEHAVIOR_SCALE) {
    247                     // TODO: How to handle resizing with a CROSSFADE (vs. REVEAL) effect
    248                     // when we are animating the view directly?
    249                     Animator anim3 = ObjectAnimator.ofObject(endDrawable, "bounds",
    250                             sRectEvaluator, startBounds, endBounds);
    251                     set.playTogether(anim3);
    252                 }
    253             }
    254             return set;
    255         } else {
    256             return null;
    257         }
    258     }
    259 
    260     private void captureValues(TransitionValues transitionValues) {
    261         View view = transitionValues.view;
    262         Rect bounds = new Rect(0, 0, view.getWidth(), view.getHeight());
    263         if (mFadeBehavior != FADE_BEHAVIOR_REVEAL) {
    264             bounds.offset(view.getLeft(), view.getTop());
    265         }
    266         transitionValues.values.put(PROPNAME_BOUNDS, bounds);
    267 
    268         if (Transition.DBG) {
    269             Log.d(LOG_TAG, "Captured bounds " + transitionValues.values.get(PROPNAME_BOUNDS));
    270         }
    271         Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
    272                 Bitmap.Config.ARGB_8888);
    273         if (view instanceof TextureView) {
    274             bitmap = ((TextureView) view).getBitmap();
    275         } else {
    276             Canvas c = new Canvas(bitmap);
    277             view.draw(c);
    278         }
    279         transitionValues.values.put(PROPNAME_BITMAP, bitmap);
    280         // TODO: I don't have resources, can't call the non-deprecated method?
    281         BitmapDrawable drawable = new BitmapDrawable(bitmap);
    282         // TODO: lrtb will be wrong if the view has transXY set
    283         drawable.setBounds(bounds);
    284         transitionValues.values.put(PROPNAME_DRAWABLE, drawable);
    285     }
    286 
    287     @Override
    288     public void captureStartValues(TransitionValues transitionValues) {
    289         captureValues(transitionValues);
    290     }
    291 
    292     @Override
    293     public void captureEndValues(TransitionValues transitionValues) {
    294         captureValues(transitionValues);
    295     }
    296 }
    297