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.content.Context;
     20 import android.util.ArrayMap;
     21 import android.util.Log;
     22 import android.view.View;
     23 import android.view.ViewGroup;
     24 import android.view.ViewTreeObserver;
     25 
     26 import java.lang.ref.WeakReference;
     27 import java.util.ArrayList;
     28 
     29 /**
     30  * This class manages the set of transitions that fire when there is a
     31  * change of {@link Scene}. To use the manager, add scenes along with
     32  * transition objects with calls to {@link #setTransition(Scene, Transition)}
     33  * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific
     34  * transitions for scene changes is not required; by default, a Scene change
     35  * will use {@link AutoTransition} to do something reasonable for most
     36  * situations. Specifying other transitions for particular scene changes is
     37  * only necessary if the application wants different transition behavior
     38  * in these situations.
     39  *
     40  * <p>TransitionManagers can be declared in XML resource files inside the
     41  * <code>res/transition</code> directory. TransitionManager resources consist of
     42  * the <code>transitionManager</code>tag name, containing one or more
     43  * <code>transition</code> tags, each of which describe the relationship of
     44  * that transition to the from/to scene information in that tag.
     45  * For example, here is a resource file that declares several scene
     46  * transitions:</p>
     47  *
     48  * {@sample development/samples/ApiDemos/res/transition/transitions_mgr.xml TransitionManager}
     49  *
     50  * <p>For each of the <code>fromScene</code> and <code>toScene</code> attributes,
     51  * there is a reference to a standard XML layout file. This is equivalent to
     52  * creating a scene from a layout in code by calling
     53  * {@link Scene#getSceneForLayout(ViewGroup, int, Context)}. For the
     54  * <code>transition</code> attribute, there is a reference to a resource
     55  * file in the <code>res/transition</code> directory which describes that
     56  * transition.</p>
     57  *
     58  * Information on XML resource descriptions for transitions can be found for
     59  * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
     60  * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade},
     61  * and {@link android.R.styleable#TransitionManager}.
     62  */
     63 public class TransitionManager {
     64     // TODO: how to handle enter/exit?
     65 
     66     private static String LOG_TAG = "TransitionManager";
     67 
     68     private static Transition sDefaultTransition = new AutoTransition();
     69 
     70     private static final String[] EMPTY_STRINGS = new String[0];
     71 
     72     ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
     73     ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
     74             new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
     75     private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
     76             sRunningTransitions =
     77             new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
     78     private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
     79 
     80 
     81     /**
     82      * Sets the transition to be used for any scene change for which no
     83      * other transition is explicitly set. The initial value is
     84      * an {@link AutoTransition} instance.
     85      *
     86      * @param transition The default transition to be used for scene changes.
     87      *
     88      * @hide pending later changes
     89      */
     90     public void setDefaultTransition(Transition transition) {
     91         sDefaultTransition = transition;
     92     }
     93 
     94     /**
     95      * Gets the current default transition. The initial value is an {@link
     96      * AutoTransition} instance.
     97      *
     98      * @return The current default transition.
     99      * @see #setDefaultTransition(Transition)
    100      *
    101      * @hide pending later changes
    102      */
    103     public static Transition getDefaultTransition() {
    104         return sDefaultTransition;
    105     }
    106 
    107     /**
    108      * Sets a specific transition to occur when the given scene is entered.
    109      *
    110      * @param scene The scene which, when applied, will cause the given
    111      * transition to run.
    112      * @param transition The transition that will play when the given scene is
    113      * entered. A value of null will result in the default behavior of
    114      * using the default transition instead.
    115      */
    116     public void setTransition(Scene scene, Transition transition) {
    117         mSceneTransitions.put(scene, transition);
    118     }
    119 
    120     /**
    121      * Sets a specific transition to occur when the given pair of scenes is
    122      * exited/entered.
    123      *
    124      * @param fromScene The scene being exited when the given transition will
    125      * be run
    126      * @param toScene The scene being entered when the given transition will
    127      * be run
    128      * @param transition The transition that will play when the given scene is
    129      * entered. A value of null will result in the default behavior of
    130      * using the default transition instead.
    131      */
    132     public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
    133         ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
    134         if (sceneTransitionMap == null) {
    135             sceneTransitionMap = new ArrayMap<Scene, Transition>();
    136             mScenePairTransitions.put(toScene, sceneTransitionMap);
    137         }
    138         sceneTransitionMap.put(fromScene, transition);
    139     }
    140 
    141     /**
    142      * Returns the Transition for the given scene being entered. The result
    143      * depends not only on the given scene, but also the scene which the
    144      * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
    145      *
    146      * @param scene The scene being entered
    147      * @return The Transition to be used for the given scene change. If no
    148      * Transition was specified for this scene change, the default transition
    149      * will be used instead.
    150      */
    151     private Transition getTransition(Scene scene) {
    152         Transition transition = null;
    153         ViewGroup sceneRoot = scene.getSceneRoot();
    154         if (sceneRoot != null) {
    155             // TODO: cached in Scene instead? long-term, cache in View itself
    156             Scene currScene = Scene.getCurrentScene(sceneRoot);
    157             if (currScene != null) {
    158                 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
    159                 if (sceneTransitionMap != null) {
    160                     transition = sceneTransitionMap.get(currScene);
    161                     if (transition != null) {
    162                         return transition;
    163                     }
    164                 }
    165             }
    166         }
    167         transition = mSceneTransitions.get(scene);
    168         return (transition != null) ? transition : sDefaultTransition;
    169     }
    170 
    171     /**
    172      * This is where all of the work of a transition/scene-change is
    173      * orchestrated. This method captures the start values for the given
    174      * transition, exits the current Scene, enters the new scene, captures
    175      * the end values for the transition, and finally plays the
    176      * resulting values-populated transition.
    177      *
    178      * @param scene The scene being entered
    179      * @param transition The transition to play for this scene change
    180      */
    181     private static void changeScene(Scene scene, Transition transition) {
    182 
    183         final ViewGroup sceneRoot = scene.getSceneRoot();
    184         if (!sPendingTransitions.contains(sceneRoot)) {
    185             sPendingTransitions.add(sceneRoot);
    186 
    187             Transition transitionClone = null;
    188             if (transition != null) {
    189                 transitionClone = transition.clone();
    190                 transitionClone.setSceneRoot(sceneRoot);
    191             }
    192 
    193             Scene oldScene = Scene.getCurrentScene(sceneRoot);
    194             if (oldScene != null && transitionClone != null &&
    195                     oldScene.isCreatedFromLayoutResource()) {
    196                 transitionClone.setCanRemoveViews(true);
    197             }
    198 
    199             sceneChangeSetup(sceneRoot, transitionClone);
    200 
    201             scene.enter();
    202 
    203             sceneChangeRunTransition(sceneRoot, transitionClone);
    204         }
    205     }
    206 
    207     private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
    208         WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
    209                 sRunningTransitions.get();
    210         if (runningTransitions == null || runningTransitions.get() == null) {
    211             ArrayMap<ViewGroup, ArrayList<Transition>> transitions =
    212                     new ArrayMap<ViewGroup, ArrayList<Transition>>();
    213             runningTransitions = new WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>(
    214                     transitions);
    215             sRunningTransitions.set(runningTransitions);
    216         }
    217         return runningTransitions.get();
    218     }
    219 
    220     private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
    221             final Transition transition) {
    222         if (transition != null && sceneRoot != null) {
    223             MultiListener listener = new MultiListener(transition, sceneRoot);
    224             sceneRoot.addOnAttachStateChangeListener(listener);
    225             sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
    226         }
    227     }
    228 
    229     /**
    230      * This private utility class is used to listen for both OnPreDraw and
    231      * OnAttachStateChange events. OnPreDraw events are the main ones we care
    232      * about since that's what triggers the transition to take place.
    233      * OnAttachStateChange events are also important in case the view is removed
    234      * from the hierarchy before the OnPreDraw event takes place; it's used to
    235      * clean up things since the OnPreDraw listener didn't get called in time.
    236      */
    237     private static class MultiListener implements ViewTreeObserver.OnPreDrawListener,
    238             View.OnAttachStateChangeListener {
    239 
    240         Transition mTransition;
    241         ViewGroup mSceneRoot;
    242 
    243         MultiListener(Transition transition, ViewGroup sceneRoot) {
    244             mTransition = transition;
    245             mSceneRoot = sceneRoot;
    246         }
    247 
    248         private void removeListeners() {
    249             mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
    250             mSceneRoot.removeOnAttachStateChangeListener(this);
    251         }
    252 
    253         @Override
    254         public void onViewAttachedToWindow(View v) {
    255         }
    256 
    257         @Override
    258         public void onViewDetachedFromWindow(View v) {
    259             removeListeners();
    260 
    261             sPendingTransitions.remove(mSceneRoot);
    262             ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot);
    263             if (runningTransitions != null && runningTransitions.size() > 0) {
    264                 for (Transition runningTransition : runningTransitions) {
    265                     runningTransition.resume(mSceneRoot);
    266                 }
    267             }
    268             mTransition.clearValues(true);
    269         }
    270 
    271         @Override
    272         public boolean onPreDraw() {
    273             removeListeners();
    274 
    275             // Don't start the transition if it's no longer pending.
    276             if (!sPendingTransitions.remove(mSceneRoot)) {
    277                 return true;
    278             }
    279 
    280             // Add to running list, handle end to remove it
    281             final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
    282                     getRunningTransitions();
    283             ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
    284             ArrayList<Transition> previousRunningTransitions = null;
    285             if (currentTransitions == null) {
    286                 currentTransitions = new ArrayList<Transition>();
    287                 runningTransitions.put(mSceneRoot, currentTransitions);
    288             } else if (currentTransitions.size() > 0) {
    289                 previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
    290             }
    291             currentTransitions.add(mTransition);
    292             mTransition.addListener(new Transition.TransitionListenerAdapter() {
    293                 @Override
    294                 public void onTransitionEnd(Transition transition) {
    295                     ArrayList<Transition> currentTransitions =
    296                             runningTransitions.get(mSceneRoot);
    297                     currentTransitions.remove(transition);
    298                 }
    299             });
    300             mTransition.captureValues(mSceneRoot, false);
    301             if (previousRunningTransitions != null) {
    302                 for (Transition runningTransition : previousRunningTransitions) {
    303                     runningTransition.resume(mSceneRoot);
    304                 }
    305             }
    306             mTransition.playTransition(mSceneRoot);
    307 
    308             return true;
    309         }
    310     };
    311 
    312     private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
    313 
    314         // Capture current values
    315         ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
    316 
    317         if (runningTransitions != null && runningTransitions.size() > 0) {
    318             for (Transition runningTransition : runningTransitions) {
    319                 runningTransition.pause(sceneRoot);
    320             }
    321         }
    322 
    323         if (transition != null) {
    324             transition.captureValues(sceneRoot, true);
    325         }
    326 
    327         // Notify previous scene that it is being exited
    328         Scene previousScene = Scene.getCurrentScene(sceneRoot);
    329         if (previousScene != null) {
    330             previousScene.exit();
    331         }
    332     }
    333 
    334     /**
    335      * Change to the given scene, using the
    336      * appropriate transition for this particular scene change
    337      * (as specified to the TransitionManager, or the default
    338      * if no such transition exists).
    339      *
    340      * @param scene The Scene to change to
    341      */
    342     public void transitionTo(Scene scene) {
    343         // Auto transition if there is no transition declared for the Scene, but there is
    344         // a root or parent view
    345         changeScene(scene, getTransition(scene));
    346     }
    347 
    348     /**
    349      * Convenience method to simply change to the given scene using
    350      * the default transition for TransitionManager.
    351      *
    352      * @param scene The Scene to change to
    353      */
    354     public static void go(Scene scene) {
    355         changeScene(scene, sDefaultTransition);
    356     }
    357 
    358     /**
    359      * Convenience method to simply change to the given scene using
    360      * the given transition.
    361      *
    362      * <p>Passing in <code>null</code> for the transition parameter will
    363      * result in the scene changing without any transition running, and is
    364      * equivalent to calling {@link Scene#exit()} on the scene root's
    365      * current scene, followed by {@link Scene#enter()} on the scene
    366      * specified by the <code>scene</code> parameter.</p>
    367      *
    368      * @param scene The Scene to change to
    369      * @param transition The transition to use for this scene change. A
    370      * value of null causes the scene change to happen with no transition.
    371      */
    372     public static void go(Scene scene, Transition transition) {
    373         changeScene(scene, transition);
    374     }
    375 
    376     /**
    377      * Convenience method to animate, using the default transition,
    378      * to a new scene defined by all changes within the given scene root between
    379      * calling this method and the next rendering frame.
    380      * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)}
    381      * with a value of <code>null</code> for the <code>transition</code> parameter.
    382      *
    383      * @param sceneRoot The root of the View hierarchy to run the transition on.
    384      */
    385     public static void beginDelayedTransition(final ViewGroup sceneRoot) {
    386         beginDelayedTransition(sceneRoot, null);
    387     }
    388 
    389     /**
    390      * Convenience method to animate to a new scene defined by all changes within
    391      * the given scene root between calling this method and the next rendering frame.
    392      * Calling this method causes TransitionManager to capture current values in the
    393      * scene root and then post a request to run a transition on the next frame.
    394      * At that time, the new values in the scene root will be captured and changes
    395      * will be animated. There is no need to create a Scene; it is implied by
    396      * changes which take place between calling this method and the next frame when
    397      * the transition begins.
    398      *
    399      * <p>Calling this method several times before the next frame (for example, if
    400      * unrelated code also wants to make dynamic changes and run a transition on
    401      * the same scene root), only the first call will trigger capturing values
    402      * and exiting the current scene. Subsequent calls to the method with the
    403      * same scene root during the same frame will be ignored.</p>
    404      *
    405      * <p>Passing in <code>null</code> for the transition parameter will
    406      * cause the TransitionManager to use its default transition.</p>
    407      *
    408      * @param sceneRoot The root of the View hierarchy to run the transition on.
    409      * @param transition The transition to use for this change. A
    410      * value of null causes the TransitionManager to use the default transition.
    411      */
    412     public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
    413         if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
    414             if (Transition.DBG) {
    415                 Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
    416                         sceneRoot + ", " + transition);
    417             }
    418             sPendingTransitions.add(sceneRoot);
    419             if (transition == null) {
    420                 transition = sDefaultTransition;
    421             }
    422             final Transition transitionClone = transition.clone();
    423             sceneChangeSetup(sceneRoot, transitionClone);
    424             Scene.setCurrentScene(sceneRoot, null);
    425             sceneChangeRunTransition(sceneRoot, transitionClone);
    426         }
    427     }
    428 
    429     /**
    430      * Ends all pending and ongoing transitions on the specified scene root.
    431      *
    432      * @param sceneRoot The root of the View hierarchy to end transitions on.
    433      */
    434     public static void endTransitions(final ViewGroup sceneRoot) {
    435         sPendingTransitions.remove(sceneRoot);
    436 
    437         final ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
    438         if (runningTransitions != null && !runningTransitions.isEmpty()) {
    439             // Make a copy in case this is called by an onTransitionEnd listener
    440             ArrayList<Transition> copy = new ArrayList(runningTransitions);
    441             for (int i = copy.size() - 1; i >= 0; i--) {
    442                 final Transition transition = copy.get(i);
    443                 transition.forceToEnd(sceneRoot);
    444             }
    445         }
    446 
    447     }
    448 }
    449