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.ObjectAnimator;
     22 import android.util.Log;
     23 import android.view.View;
     24 import android.view.ViewGroup;
     25 
     26 /**
     27  * This transition tracks changes to the visibility of target views in the
     28  * start and end scenes and fades views in or out when they become visible
     29  * or non-visible. Visibility is determined by both the
     30  * {@link View#setVisibility(int)} state of the view as well as whether it
     31  * is parented in the current view hierarchy.
     32  *
     33  * <p>The ability of this transition to fade out a particular view, and the
     34  * way that that fading operation takes place, is based on
     35  * the situation of the view in the view hierarchy. For example, if a view was
     36  * simply removed from its parent, then the view will be added into a {@link
     37  * android.view.ViewGroupOverlay} while fading. If a visible view is
     38  * changed to be {@link View#GONE} or {@link View#INVISIBLE}, then the
     39  * visibility will be changed to {@link View#VISIBLE} for the duration of
     40  * the animation. However, if a view is in a hierarchy which is also altering
     41  * its visibility, the situation can be more complicated. In general, if a
     42  * view that is no longer in the hierarchy in the end scene still has a
     43  * parent (so its parent hierarchy was removed, but it was not removed from
     44  * its parent), then it will be left alone to avoid side-effects from
     45  * improperly removing it from its parent. The only exception to this is if
     46  * the previous {@link Scene} was
     47  * {@link Scene#getSceneForLayout(android.view.ViewGroup, int, android.content.Context)
     48  * created from a layout resource file}, then it is considered safe to un-parent
     49  * the starting scene view in order to fade it out.</p>
     50  *
     51  * <p>A Fade transition can be described in a resource file by using the
     52  * tag <code>fade</code>, along with the standard
     53  * attributes of {@link android.R.styleable#Fade} and
     54  * {@link android.R.styleable#Transition}.</p>
     55 
     56  */
     57 public class Fade extends Visibility {
     58 
     59     private static boolean DBG = Transition.DBG && false;
     60 
     61     private static final String LOG_TAG = "Fade";
     62     private static final String PROPNAME_SCREEN_X = "android:fade:screenX";
     63     private static final String PROPNAME_SCREEN_Y = "android:fade:screenY";
     64 
     65     /**
     66      * Fading mode used in {@link #Fade(int)} to make the transition
     67      * operate on targets that are appearing. Maybe be combined with
     68      * {@link #OUT} to fade both in and out.
     69      */
     70     public static final int IN = 0x1;
     71     /**
     72      * Fading mode used in {@link #Fade(int)} to make the transition
     73      * operate on targets that are disappearing. Maybe be combined with
     74      * {@link #IN} to fade both in and out.
     75      */
     76     public static final int OUT = 0x2;
     77 
     78     private int mFadingMode;
     79 
     80     /**
     81      * Constructs a Fade transition that will fade targets in and out.
     82      */
     83     public Fade() {
     84         this(IN | OUT);
     85     }
     86 
     87     /**
     88      * Constructs a Fade transition that will fade targets in
     89      * and/or out, according to the value of fadingMode.
     90      *
     91      * @param fadingMode The behavior of this transition, a combination of
     92      * {@link #IN} and {@link #OUT}.
     93      */
     94     public Fade(int fadingMode) {
     95         mFadingMode = fadingMode;
     96     }
     97 
     98     /**
     99      * Utility method to handle creating and running the Animator.
    100      */
    101     private Animator createAnimation(View view, float startAlpha, float endAlpha,
    102             AnimatorListenerAdapter listener) {
    103         if (startAlpha == endAlpha) {
    104             // run listener if we're noop'ing the animation, to get the end-state results now
    105             if (listener != null) {
    106                 listener.onAnimationEnd(null);
    107             }
    108             return null;
    109         }
    110         final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", startAlpha,
    111                 endAlpha);
    112         if (DBG) {
    113             Log.d(LOG_TAG, "Created animator " + anim);
    114         }
    115         if (listener != null) {
    116             anim.addListener(listener);
    117             anim.addPauseListener(listener);
    118         }
    119         return anim;
    120     }
    121 
    122     private void captureValues(TransitionValues transitionValues) {
    123         int[] loc = new int[2];
    124         transitionValues.view.getLocationOnScreen(loc);
    125         transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]);
    126         transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]);
    127     }
    128 
    129     @Override
    130     public void captureStartValues(TransitionValues transitionValues) {
    131         super.captureStartValues(transitionValues);
    132         captureValues(transitionValues);
    133     }
    134 
    135     @Override
    136     public Animator onAppear(ViewGroup sceneRoot,
    137             TransitionValues startValues, int startVisibility,
    138             TransitionValues endValues, int endVisibility) {
    139         if ((mFadingMode & IN) != IN || endValues == null) {
    140             return null;
    141         }
    142         final View endView = endValues.view;
    143         if (DBG) {
    144             View startView = (startValues != null) ? startValues.view : null;
    145             Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
    146                     startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
    147         }
    148         endView.setTransitionAlpha(0);
    149         TransitionListener transitionListener = new TransitionListenerAdapter() {
    150             boolean mCanceled = false;
    151             float mPausedAlpha;
    152 
    153             @Override
    154             public void onTransitionCancel(Transition transition) {
    155                 endView.setTransitionAlpha(1);
    156                 mCanceled = true;
    157             }
    158 
    159             @Override
    160             public void onTransitionEnd(Transition transition) {
    161                 if (!mCanceled) {
    162                     endView.setTransitionAlpha(1);
    163                 }
    164             }
    165 
    166             @Override
    167             public void onTransitionPause(Transition transition) {
    168                 mPausedAlpha = endView.getTransitionAlpha();
    169                 endView.setTransitionAlpha(1);
    170             }
    171 
    172             @Override
    173             public void onTransitionResume(Transition transition) {
    174                 endView.setTransitionAlpha(mPausedAlpha);
    175             }
    176         };
    177         addListener(transitionListener);
    178         return createAnimation(endView, 0, 1, null);
    179     }
    180 
    181     @Override
    182     public Animator onDisappear(ViewGroup sceneRoot,
    183             TransitionValues startValues, int startVisibility,
    184             TransitionValues endValues, int endVisibility) {
    185         if ((mFadingMode & OUT) != OUT) {
    186             return null;
    187         }
    188         View view = null;
    189         View startView = (startValues != null) ? startValues.view : null;
    190         View endView = (endValues != null) ? endValues.view : null;
    191         if (DBG) {
    192             Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " +
    193                         startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
    194         }
    195         View overlayView = null;
    196         View viewToKeep = null;
    197         if (endView == null || endView.getParent() == null) {
    198             if (endView != null) {
    199                 // endView was removed from its parent - add it to the overlay
    200                 view = overlayView = endView;
    201             } else if (startView != null) {
    202                 // endView does not exist. Use startView only under certain
    203                 // conditions, because placing a view in an overlay necessitates
    204                 // it being removed from its current parent
    205                 if (startView.getParent() == null) {
    206                     // no parent - safe to use
    207                     view = overlayView = startView;
    208                 } else if (startView.getParent() instanceof View &&
    209                         startView.getParent().getParent() == null) {
    210                     View startParent = (View) startView.getParent();
    211                     int id = startParent.getId();
    212                     if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
    213                         // no parent, but its parent is unparented  but the parent
    214                         // hierarchy has been replaced by a new hierarchy with the same id
    215                         // and it is safe to un-parent startView
    216                         view = overlayView = startView;
    217                     }
    218                 }
    219             }
    220         } else {
    221             // visibility change
    222             if (endVisibility == View.INVISIBLE) {
    223                 view = endView;
    224                 viewToKeep = view;
    225             } else {
    226                 // Becoming GONE
    227                 if (startView == endView) {
    228                     view = endView;
    229                     viewToKeep = view;
    230                 } else {
    231                     view = startView;
    232                     overlayView = view;
    233                 }
    234             }
    235         }
    236         final int finalVisibility = endVisibility;
    237         // TODO: add automatic facility to Visibility superclass for keeping views around
    238         if (overlayView != null) {
    239             // TODO: Need to do this for general case of adding to overlay
    240             int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X);
    241             int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y);
    242             int[] loc = new int[2];
    243             sceneRoot.getLocationOnScreen(loc);
    244             overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
    245             overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
    246             sceneRoot.getOverlay().add(overlayView);
    247             // TODO: add automatic facility to Visibility superclass for keeping views around
    248             final float startAlpha = 1;
    249             float endAlpha = 0;
    250             final View finalView = view;
    251             final View finalOverlayView = overlayView;
    252             final View finalViewToKeep = viewToKeep;
    253             final ViewGroup finalSceneRoot = sceneRoot;
    254             final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
    255                 @Override
    256                 public void onAnimationEnd(Animator animation) {
    257                     finalView.setTransitionAlpha(startAlpha);
    258                     // TODO: restore view offset from overlay repositioning
    259                     if (finalViewToKeep != null) {
    260                         finalViewToKeep.setVisibility(finalVisibility);
    261                     }
    262                     if (finalOverlayView != null) {
    263                         finalSceneRoot.getOverlay().remove(finalOverlayView);
    264                     }
    265                 }
    266 
    267                 @Override
    268                 public void onAnimationPause(Animator animation) {
    269                     if (finalOverlayView != null) {
    270                         finalSceneRoot.getOverlay().remove(finalOverlayView);
    271                     }
    272                 }
    273 
    274                 @Override
    275                 public void onAnimationResume(Animator animation) {
    276                     if (finalOverlayView != null) {
    277                         finalSceneRoot.getOverlay().add(finalOverlayView);
    278                     }
    279                 }
    280             };
    281             return createAnimation(view, startAlpha, endAlpha, endListener);
    282         }
    283         if (viewToKeep != null) {
    284             // TODO: find a different way to do this, like just changing the view to be
    285             // VISIBLE for the duration of the transition
    286             viewToKeep.setVisibility((View.VISIBLE));
    287             // TODO: add automatic facility to Visibility superclass for keeping views around
    288             final float startAlpha = 1;
    289             float endAlpha = 0;
    290             final View finalView = view;
    291             final View finalOverlayView = overlayView;
    292             final View finalViewToKeep = viewToKeep;
    293             final ViewGroup finalSceneRoot = sceneRoot;
    294             final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
    295                 boolean mCanceled = false;
    296                 float mPausedAlpha = -1;
    297 
    298                 @Override
    299                 public void onAnimationPause(Animator animation) {
    300                     if (finalViewToKeep != null && !mCanceled) {
    301                         finalViewToKeep.setVisibility(finalVisibility);
    302                     }
    303                     mPausedAlpha = finalView.getTransitionAlpha();
    304                     finalView.setTransitionAlpha(startAlpha);
    305                 }
    306 
    307                 @Override
    308                 public void onAnimationResume(Animator animation) {
    309                     if (finalViewToKeep != null && !mCanceled) {
    310                         finalViewToKeep.setVisibility(View.VISIBLE);
    311                     }
    312                     finalView.setTransitionAlpha(mPausedAlpha);
    313                 }
    314 
    315                 @Override
    316                 public void onAnimationCancel(Animator animation) {
    317                     mCanceled = true;
    318                     if (mPausedAlpha >= 0) {
    319                         finalView.setTransitionAlpha(mPausedAlpha);
    320                     }
    321                 }
    322 
    323                 @Override
    324                 public void onAnimationEnd(Animator animation) {
    325                     if (!mCanceled) {
    326                         finalView.setTransitionAlpha(startAlpha);
    327                     }
    328                     // TODO: restore view offset from overlay repositioning
    329                     if (finalViewToKeep != null && !mCanceled) {
    330                         finalViewToKeep.setVisibility(finalVisibility);
    331                     }
    332                     if (finalOverlayView != null) {
    333                         finalSceneRoot.getOverlay().remove(finalOverlayView);
    334                     }
    335                 }
    336             };
    337             return createAnimation(view, startAlpha, endAlpha, endListener);
    338         }
    339         return null;
    340     }
    341 
    342 }