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     ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>();
     71     ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions =
     72             new ArrayMap<Scene, ArrayMap<Scene, Transition>>();
     73     private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>
     74             sRunningTransitions =
     75             new ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>>();
     76     private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>();
     77 
     78 
     79     /**
     80      * Sets the transition to be used for any scene change for which no
     81      * other transition is explicitly set. The initial value is
     82      * an {@link AutoTransition} instance.
     83      *
     84      * @param transition The default transition to be used for scene changes.
     85      *
     86      * @hide pending later changes
     87      */
     88     public void setDefaultTransition(Transition transition) {
     89         sDefaultTransition = transition;
     90     }
     91 
     92     /**
     93      * Gets the current default transition. The initial value is an {@link
     94      * AutoTransition} instance.
     95      *
     96      * @return The current default transition.
     97      * @see #setDefaultTransition(Transition)
     98      *
     99      * @hide pending later changes
    100      */
    101     public static Transition getDefaultTransition() {
    102         return sDefaultTransition;
    103     }
    104 
    105     /**
    106      * Sets a specific transition to occur when the given scene is entered.
    107      *
    108      * @param scene The scene which, when applied, will cause the given
    109      * transition to run.
    110      * @param transition The transition that will play when the given scene is
    111      * entered. A value of null will result in the default behavior of
    112      * using the default transition instead.
    113      */
    114     public void setTransition(Scene scene, Transition transition) {
    115         mSceneTransitions.put(scene, transition);
    116     }
    117 
    118     /**
    119      * Sets a specific transition to occur when the given pair of scenes is
    120      * exited/entered.
    121      *
    122      * @param fromScene The scene being exited when the given transition will
    123      * be run
    124      * @param toScene The scene being entered when the given transition will
    125      * be run
    126      * @param transition The transition that will play when the given scene is
    127      * entered. A value of null will result in the default behavior of
    128      * using the default transition instead.
    129      */
    130     public void setTransition(Scene fromScene, Scene toScene, Transition transition) {
    131         ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene);
    132         if (sceneTransitionMap == null) {
    133             sceneTransitionMap = new ArrayMap<Scene, Transition>();
    134             mScenePairTransitions.put(toScene, sceneTransitionMap);
    135         }
    136         sceneTransitionMap.put(fromScene, transition);
    137     }
    138 
    139     /**
    140      * Returns the Transition for the given scene being entered. The result
    141      * depends not only on the given scene, but also the scene which the
    142      * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in.
    143      *
    144      * @param scene The scene being entered
    145      * @return The Transition to be used for the given scene change. If no
    146      * Transition was specified for this scene change, the default transition
    147      * will be used instead.
    148      */
    149     private Transition getTransition(Scene scene) {
    150         Transition transition = null;
    151         ViewGroup sceneRoot = scene.getSceneRoot();
    152         if (sceneRoot != null) {
    153             // TODO: cached in Scene instead? long-term, cache in View itself
    154             Scene currScene = Scene.getCurrentScene(sceneRoot);
    155             if (currScene != null) {
    156                 ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene);
    157                 if (sceneTransitionMap != null) {
    158                     transition = sceneTransitionMap.get(currScene);
    159                     if (transition != null) {
    160                         return transition;
    161                     }
    162                 }
    163             }
    164         }
    165         transition = mSceneTransitions.get(scene);
    166         return (transition != null) ? transition : sDefaultTransition;
    167     }
    168 
    169     /**
    170      * This is where all of the work of a transition/scene-change is
    171      * orchestrated. This method captures the start values for the given
    172      * transition, exits the current Scene, enters the new scene, captures
    173      * the end values for the transition, and finally plays the
    174      * resulting values-populated transition.
    175      *
    176      * @param scene The scene being entered
    177      * @param transition The transition to play for this scene change
    178      */
    179     private static void changeScene(Scene scene, Transition transition) {
    180 
    181         final ViewGroup sceneRoot = scene.getSceneRoot();
    182 
    183         Transition transitionClone = transition.clone();
    184         transitionClone.setSceneRoot(sceneRoot);
    185 
    186         Scene oldScene = Scene.getCurrentScene(sceneRoot);
    187         if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
    188             transitionClone.setCanRemoveViews(true);
    189         }
    190 
    191         sceneChangeSetup(sceneRoot, transitionClone);
    192 
    193         scene.enter();
    194 
    195         sceneChangeRunTransition(sceneRoot, transitionClone);
    196     }
    197 
    198     private static ArrayMap<ViewGroup, ArrayList<Transition>> getRunningTransitions() {
    199         WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>> runningTransitions =
    200                 sRunningTransitions.get();
    201         if (runningTransitions == null || runningTransitions.get() == null) {
    202             ArrayMap<ViewGroup, ArrayList<Transition>> transitions =
    203                     new ArrayMap<ViewGroup, ArrayList<Transition>>();
    204             runningTransitions = new WeakReference<ArrayMap<ViewGroup, ArrayList<Transition>>>(
    205                     transitions);
    206             sRunningTransitions.set(runningTransitions);
    207         }
    208         return runningTransitions.get();
    209     }
    210 
    211     private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
    212             final Transition transition) {
    213         if (transition != null && sceneRoot != null) {
    214             MultiListener listener = new MultiListener(transition, sceneRoot);
    215             sceneRoot.addOnAttachStateChangeListener(listener);
    216             sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
    217         }
    218     }
    219 
    220     /**
    221      * This private utility class is used to listen for both OnPreDraw and
    222      * OnAttachStateChange events. OnPreDraw events are the main ones we care
    223      * about since that's what triggers the transition to take place.
    224      * OnAttachStateChange events are also important in case the view is removed
    225      * from the hierarchy before the OnPreDraw event takes place; it's used to
    226      * clean up things since the OnPreDraw listener didn't get called in time.
    227      */
    228     private static class MultiListener implements ViewTreeObserver.OnPreDrawListener,
    229             View.OnAttachStateChangeListener {
    230 
    231         Transition mTransition;
    232         ViewGroup mSceneRoot;
    233 
    234         MultiListener(Transition transition, ViewGroup sceneRoot) {
    235             mTransition = transition;
    236             mSceneRoot = sceneRoot;
    237         }
    238 
    239         private void removeListeners() {
    240             mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
    241             mSceneRoot.removeOnAttachStateChangeListener(this);
    242         }
    243 
    244         @Override
    245         public void onViewAttachedToWindow(View v) {
    246         }
    247 
    248         @Override
    249         public void onViewDetachedFromWindow(View v) {
    250             removeListeners();
    251 
    252             sPendingTransitions.remove(mSceneRoot);
    253             ArrayList<Transition> runningTransitions = getRunningTransitions().get(mSceneRoot);
    254             if (runningTransitions != null && runningTransitions.size() > 0) {
    255                 for (Transition runningTransition : runningTransitions) {
    256                     runningTransition.resume();
    257                 }
    258             }
    259             mTransition.clearValues(true);
    260         }
    261 
    262         @Override
    263         public boolean onPreDraw() {
    264             removeListeners();
    265             sPendingTransitions.remove(mSceneRoot);
    266             // Add to running list, handle end to remove it
    267             final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
    268                     getRunningTransitions();
    269             ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
    270             ArrayList<Transition> previousRunningTransitions = null;
    271             if (currentTransitions == null) {
    272                 currentTransitions = new ArrayList<Transition>();
    273                 runningTransitions.put(mSceneRoot, currentTransitions);
    274             } else if (currentTransitions.size() > 0) {
    275                 previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
    276             }
    277             currentTransitions.add(mTransition);
    278             mTransition.addListener(new Transition.TransitionListenerAdapter() {
    279                 @Override
    280                 public void onTransitionEnd(Transition transition) {
    281                     ArrayList<Transition> currentTransitions =
    282                             runningTransitions.get(mSceneRoot);
    283                     currentTransitions.remove(transition);
    284                 }
    285             });
    286             mTransition.captureValues(mSceneRoot, false);
    287             if (previousRunningTransitions != null) {
    288                 for (Transition runningTransition : previousRunningTransitions) {
    289                     runningTransition.resume();
    290                 }
    291             }
    292             mTransition.playTransition(mSceneRoot);
    293 
    294             return true;
    295         }
    296     };
    297 
    298     private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
    299 
    300         // Capture current values
    301         ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
    302 
    303         if (runningTransitions != null && runningTransitions.size() > 0) {
    304             for (Transition runningTransition : runningTransitions) {
    305                 runningTransition.pause();
    306             }
    307         }
    308 
    309         if (transition != null) {
    310             transition.captureValues(sceneRoot, true);
    311         }
    312 
    313         // Notify previous scene that it is being exited
    314         Scene previousScene = Scene.getCurrentScene(sceneRoot);
    315         if (previousScene != null) {
    316             previousScene.exit();
    317         }
    318     }
    319 
    320     /**
    321      * Change to the given scene, using the
    322      * appropriate transition for this particular scene change
    323      * (as specified to the TransitionManager, or the default
    324      * if no such transition exists).
    325      *
    326      * @param scene The Scene to change to
    327      */
    328     public void transitionTo(Scene scene) {
    329         // Auto transition if there is no transition declared for the Scene, but there is
    330         // a root or parent view
    331         changeScene(scene, getTransition(scene));
    332 
    333     }
    334 
    335     /**
    336      * Convenience method to simply change to the given scene using
    337      * the default transition for TransitionManager.
    338      *
    339      * @param scene The Scene to change to
    340      */
    341     public static void go(Scene scene) {
    342         changeScene(scene, sDefaultTransition);
    343     }
    344 
    345     /**
    346      * Convenience method to simply change to the given scene using
    347      * the given transition.
    348      *
    349      * <p>Passing in <code>null</code> for the transition parameter will
    350      * result in the scene changing without any transition running, and is
    351      * equivalent to calling {@link Scene#exit()} on the scene root's
    352      * current scene, followed by {@link Scene#enter()} on the scene
    353      * specified by the <code>scene</code> parameter.</p>
    354      *
    355      * @param scene The Scene to change to
    356      * @param transition The transition to use for this scene change. A
    357      * value of null causes the scene change to happen with no transition.
    358      */
    359     public static void go(Scene scene, Transition transition) {
    360         changeScene(scene, transition);
    361     }
    362 
    363     /**
    364      * Convenience method to animate, using the default transition,
    365      * to a new scene defined by all changes within the given scene root between
    366      * calling this method and the next rendering frame.
    367      * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)}
    368      * with a value of <code>null</code> for the <code>transition</code> parameter.
    369      *
    370      * @param sceneRoot The root of the View hierarchy to run the transition on.
    371      */
    372     public static void beginDelayedTransition(final ViewGroup sceneRoot) {
    373         beginDelayedTransition(sceneRoot, null);
    374     }
    375 
    376     /**
    377      * Convenience method to animate to a new scene defined by all changes within
    378      * the given scene root between calling this method and the next rendering frame.
    379      * Calling this method causes TransitionManager to capture current values in the
    380      * scene root and then post a request to run a transition on the next frame.
    381      * At that time, the new values in the scene root will be captured and changes
    382      * will be animated. There is no need to create a Scene; it is implied by
    383      * changes which take place between calling this method and the next frame when
    384      * the transition begins.
    385      *
    386      * <p>Calling this method several times before the next frame (for example, if
    387      * unrelated code also wants to make dynamic changes and run a transition on
    388      * the same scene root), only the first call will trigger capturing values
    389      * and exiting the current scene. Subsequent calls to the method with the
    390      * same scene root during the same frame will be ignored.</p>
    391      *
    392      * <p>Passing in <code>null</code> for the transition parameter will
    393      * cause the TransitionManager to use its default transition.</p>
    394      *
    395      * @param sceneRoot The root of the View hierarchy to run the transition on.
    396      * @param transition The transition to use for this change. A
    397      * value of null causes the TransitionManager to use the default transition.
    398      */
    399     public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
    400         if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
    401             if (Transition.DBG) {
    402                 Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
    403                         sceneRoot + ", " + transition);
    404             }
    405             sPendingTransitions.add(sceneRoot);
    406             if (transition == null) {
    407                 transition = sDefaultTransition;
    408             }
    409             final Transition transitionClone = transition.clone();
    410             sceneChangeSetup(sceneRoot, transitionClone);
    411             Scene.setCurrentScene(sceneRoot, null);
    412             sceneChangeRunTransition(sceneRoot, transitionClone);
    413         }
    414     }
    415 }
    416