Java tutorial
/* * 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 org.ayo.animate.transition; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.support.v4.util.ArrayMap; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import org.ayo.animate.R; import org.ayo.animate.transition.utils.ViewGroupOverlayUtils; import org.ayo.animate.transition.utils.ViewGroupUtils; import org.ayo.animate.transition.utils.ViewUtils; import java.util.ArrayList; /** * This class manages the set of transitions that fire when there is a * change of {@link Scene}. To use the manager, add scenes along with * transition objects with calls to {@link #setTransition(Scene, Transition)} * or {@link #setTransition(Scene, Scene, Transition)}. Setting specific * transitions for scene changes is not required; by default, a Scene change * will use {@link AutoTransition} to do something reasonable for most * situations. Specifying other transitions for particular scene changes is * only necessary if the application wants different transition behavior * in these situations. * <p/> * <p>TransitionManagers can be declared in XML resource files inside the * <code>res/transition</code> directory. TransitionManager resources consist of * the <code>transitionManager</code>tag name, containing one or more * <code>transition</code> tags, each of which describe the relationship of * that transition to the from/to scene information in that tag. * For example, here is a resource file that declares several scene * transitions:</p> * <p/> * {@sample development/samples/ApiDemos/res/transition/transitions_mgr.xml TransitionManager} * <p/> * <p>For each of the <code>fromScene</code> and <code>toScene</code> attributes, * there is a reference to a standard XML layout file. This is equivalent to * creating a scene from a layout in code by calling * {@link Scene#getSceneForLayout(ViewGroup, int, Context)}. For the * <code>transition</code> attribute, there is a reference to a resource * file in the <code>res/transition</code> directory which describes that * transition.</p> * <p/> * Information on XML resource descriptions for transitions can be found for * {@link com.transitionseverywhere.R.styleable#Transition}, {@link com.transitionseverywhere.R.styleable#TransitionSet}, * {@link com.transitionseverywhere.R.styleable#TransitionTarget}, {@link com.transitionseverywhere.R.styleable#Fade}, * and {@link com.transitionseverywhere.R.styleable#TransitionManager}. */ public class TransitionManager { private static String LOG_TAG = "TransitionManager"; private static Transition sDefaultTransition = new AutoTransition(); private static final String[] EMPTY_STRINGS = new String[0]; ArrayMap<Scene, Transition> mSceneTransitions = new ArrayMap<Scene, Transition>(); ArrayMap<Scene, ArrayMap<Scene, Transition>> mScenePairTransitions = new ArrayMap<Scene, ArrayMap<Scene, Transition>>(); private static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<ViewGroup>(); /** * Sets the transition to be used for any scene change for which no * other transition is explicitly set. The initial value is * an {@link AutoTransition} instance. * * @param transition The default transition to be used for scene changes. * @hide pending later changes */ public void setDefaultTransition(Transition transition) { sDefaultTransition = transition; } /** * Gets the current default transition. The initial value is an {@link * AutoTransition} instance. * * @return The current default transition. * @hide pending later changes * @see #setDefaultTransition(Transition) */ public static Transition getDefaultTransition() { return sDefaultTransition; } /** * Sets a specific transition to occur when the given scene is entered. * * @param scene The scene which, when applied, will cause the given * transition to run. * @param transition The transition that will play when the given scene is * entered. A value of null will result in the default behavior of * using the default transition instead. */ public void setTransition(Scene scene, Transition transition) { mSceneTransitions.put(scene, transition); } /** * Sets a specific transition to occur when the given pair of scenes is * exited/entered. * * @param fromScene The scene being exited when the given transition will * be run * @param toScene The scene being entered when the given transition will * be run * @param transition The transition that will play when the given scene is * entered. A value of null will result in the default behavior of * using the default transition instead. */ public void setTransition(Scene fromScene, Scene toScene, Transition transition) { ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(toScene); if (sceneTransitionMap == null) { sceneTransitionMap = new ArrayMap<Scene, Transition>(); mScenePairTransitions.put(toScene, sceneTransitionMap); } sceneTransitionMap.put(fromScene, transition); } /** * Returns the Transition for the given scene being entered. The result * depends not only on the given scene, but also the scene which the * {@link Scene#getSceneRoot() sceneRoot} of the Scene is currently in. * * @param scene The scene being entered * @return The Transition to be used for the given scene change. If no * Transition was specified for this scene change, the default transition * will be used instead. */ private Transition getTransition(Scene scene) { Transition transition = null; ViewGroup sceneRoot = scene.getSceneRoot(); if (sceneRoot != null) { // TODO: cached in Scene instead? long-term, cache in View itself Scene currScene = Scene.getCurrentScene(sceneRoot); if (currScene != null) { ArrayMap<Scene, Transition> sceneTransitionMap = mScenePairTransitions.get(scene); if (sceneTransitionMap != null) { transition = sceneTransitionMap.get(currScene); if (transition != null) { return transition; } } } } transition = mSceneTransitions.get(scene); return (transition != null) ? transition : sDefaultTransition; } /** * This is where all of the work of a transition/scene-change is * orchestrated. This method captures the start values for the given * transition, exits the current Scene, enters the new scene, captures * the end values for the transition, and finally plays the * resulting values-populated transition. * * @param scene The scene being entered * @param transition The transition to play for this scene change */ private static void changeScene(Scene scene, Transition transition) { final ViewGroup sceneRoot = scene.getSceneRoot(); if (!sPendingTransitions.contains(sceneRoot)) { Transition transitionClone = null; if (isTransitionsAllowed()) { sPendingTransitions.add(sceneRoot); if (transition != null) { transitionClone = transition.clone(); transitionClone.setSceneRoot(sceneRoot); } Scene oldScene = Scene.getCurrentScene(sceneRoot); if (oldScene != null && transitionClone != null && oldScene.isCreatedFromLayoutResource()) { transitionClone.setCanRemoveViews(true); } } sceneChangeSetup(sceneRoot, transitionClone); scene.enter(); sceneChangeRunTransition(sceneRoot, transitionClone); } } private static ArrayList<Transition> getRunningTransitions(ViewGroup viewGroup) { ArrayList<Transition> transitions = (ArrayList<Transition>) viewGroup.getTag(R.id.runningTransitions); if (transitions == null) { transitions = new ArrayList<Transition>(); viewGroup.setTag(R.id.runningTransitions, transitions); } return transitions; } @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private static void sceneChangeRunTransition(final ViewGroup sceneRoot, final Transition transition) { if (transition != null && sceneRoot != null && isTransitionsAllowed()) { ViewGroupOverlayUtils.initializeOverlay(sceneRoot); MultiListener listener = new MultiListener(transition, sceneRoot); sceneRoot.addOnAttachStateChangeListener(listener); sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener); } else { sPendingTransitions.remove(sceneRoot); } } /** * This private utility class is used to listen for both OnPreDraw and * OnAttachStateChange events. OnPreDraw events are the main ones we care * about since that's what triggers the transition to take place. * OnAttachStateChange events are also important in case the view is removed * from the hierarchy before the OnPreDraw event takes place; it's used to * clean up things since the OnPreDraw listener didn't get called in time. */ @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) private static class MultiListener implements ViewTreeObserver.OnPreDrawListener, View.OnAttachStateChangeListener { Transition mTransition; ViewGroup mSceneRoot; MultiListener(Transition transition, ViewGroup sceneRoot) { mTransition = transition; mSceneRoot = sceneRoot; } private void removeListeners() { mSceneRoot.getViewTreeObserver().removeOnPreDrawListener(this); mSceneRoot.removeOnAttachStateChangeListener(this); } @Override public void onViewAttachedToWindow(View v) { } @Override public void onViewDetachedFromWindow(View v) { removeListeners(); sPendingTransitions.remove(mSceneRoot); ArrayList<Transition> runningTransitions = getRunningTransitions(mSceneRoot); if (runningTransitions.size() > 0) { for (Transition runningTransition : runningTransitions) { runningTransition.resume(mSceneRoot); } } mTransition.clearValues(true); } @Override public boolean onPreDraw() { removeListeners(); // Don't start the transition if it's no longer pending. if (!sPendingTransitions.remove(mSceneRoot)) { return true; } // Add to running list, handle end to remove it ArrayList<Transition> currentTransitions = getRunningTransitions(mSceneRoot); ArrayList<Transition> previousRunningTransitions = null; if (currentTransitions.size() > 0) { previousRunningTransitions = new ArrayList<Transition>(currentTransitions); } currentTransitions.add(mTransition); mTransition.addListener(new Transition.TransitionListenerAdapter() { @Override public void onTransitionEnd(Transition transition) { ArrayList<Transition> currentTransitions = getRunningTransitions(mSceneRoot); currentTransitions.remove(transition); } }); boolean somethingCanBeChanged = cancelAllSystemLayoutTransitions(mSceneRoot); mTransition.captureValues(mSceneRoot, false); if (previousRunningTransitions != null) { for (Transition runningTransition : previousRunningTransitions) { runningTransition.resume(mSceneRoot); } } mTransition.playTransition(mSceneRoot); return !somethingCanBeChanged; } } private static boolean cancelAllSystemLayoutTransitions(View view) { boolean canceled = false; if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; canceled = ViewGroupUtils.cancelLayoutTransition(viewGroup); for (int i = 0; i < viewGroup.getChildCount(); i++) { canceled = cancelAllSystemLayoutTransitions(viewGroup.getChildAt(i)) || canceled; } } return canceled; } private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) { if (isTransitionsAllowed()) { // Capture current values ArrayList<Transition> runningTransitions = getRunningTransitions(sceneRoot); if (runningTransitions.size() > 0) { for (Transition runningTransition : runningTransitions) { runningTransition.pause(sceneRoot); } } if (transition != null) { transition.captureValues(sceneRoot, true); } } // Notify previous scene that it is being exited Scene previousScene = Scene.getCurrentScene(sceneRoot); if (previousScene != null) { previousScene.exit(); } } /** * Change to the given scene, using the * appropriate transition for this particular scene change * (as specified to the TransitionManager, or the default * if no such transition exists). * * @param scene The Scene to change to */ public void transitionTo(Scene scene) { // Auto transition if there is no transition declared for the Scene, but there is // a root or parent view changeScene(scene, getTransition(scene)); } /** * Convenience method to simply change to the given scene using * the default transition for TransitionManager. * * @param scene The Scene to change to */ public static void go(Scene scene) { changeScene(scene, sDefaultTransition); } /** * Convenience method to simply change to the given scene using * the given transition. * <p/> * <p>Passing in <code>null</code> for the transition parameter will * result in the scene changing without any transition running, and is * equivalent to calling {@link Scene#exit()} on the scene root's * current scene, followed by {@link Scene#enter()} on the scene * specified by the <code>scene</code> parameter.</p> * * @param scene The Scene to change to * @param transition The transition to use for this scene change. A * value of null causes the scene change to happen with no transition. */ public static void go(Scene scene, Transition transition) { changeScene(scene, transition); } /** * Convenience method to animate, using the default transition, * to a new scene defined by all changes within the given scene root between * calling this method and the next rendering frame. * Equivalent to calling {@link #beginDelayedTransition(ViewGroup, Transition)} * with a value of <code>null</code> for the <code>transition</code> parameter. * * @param sceneRoot The root of the View hierarchy to run the transition on. */ public static void beginDelayedTransition(final ViewGroup sceneRoot) { beginDelayedTransition(sceneRoot, null); } /** * Convenience method to animate to a new scene defined by all changes within * the given scene root between calling this method and the next rendering frame. * Calling this method causes TransitionManager to capture current values in the * scene root and then post a request to run a transition on the next frame. * At that time, the new values in the scene root will be captured and changes * will be animated. There is no need to create a Scene; it is implied by * changes which take place between calling this method and the next frame when * the transition begins. * <p/> * <p>Calling this method several times before the next frame (for example, if * unrelated code also wants to make dynamic changes and run a transition on * the same scene root), only the first call will trigger capturing values * and exiting the current scene. Subsequent calls to the method with the * same scene root during the same frame will be ignored.</p> * <p/> * <p>Passing in <code>null</code> for the transition parameter will * cause the TransitionManager to use its default transition.</p> * * @param sceneRoot The root of the View hierarchy to run the transition on. * @param transition The transition to use for this change. A * value of null causes the TransitionManager to use the default transition. */ public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) { if (!sPendingTransitions.contains(sceneRoot) && ViewUtils.isLaidOut(sceneRoot, true)) { if (Transition.DBG) { Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " + sceneRoot + ", " + transition); } sPendingTransitions.add(sceneRoot); if (transition == null) { transition = sDefaultTransition; } final Transition transitionClone = transition.clone(); sceneChangeSetup(sceneRoot, transitionClone); Scene.setCurrentScene(sceneRoot, null); sceneChangeRunTransition(sceneRoot, transitionClone); } } /** * Ends all pending and ongoing transitions on the specified scene root. * * @param sceneRoot The root of the View hierarchy to end transitions on. */ public static void endTransitions(final ViewGroup sceneRoot) { sPendingTransitions.remove(sceneRoot); final ArrayList<Transition> runningTransitions = getRunningTransitions(sceneRoot); if (runningTransitions != null && !runningTransitions.isEmpty()) { // Make a copy in case this is called by an onTransitionEnd listener ArrayList<Transition> copy = new ArrayList(runningTransitions); for (int i = copy.size() - 1; i >= 0; i--) { final Transition transition = copy.get(i); transition.end(); } } } /** * Returns is transition animations enabled. Animations was disabled * for Android versions < 4.0 */ public static boolean isTransitionsAllowed() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; } /** * Sets the name of the View to be used to identify Views in Transitions. * Names should be unique in the View hierarchy. * * @param transitionName The name of the View to uniquely identify it for Transitions. */ public static void setTransitionName(View v, String transitionName) { ViewUtils.setTransitionName(v, transitionName); } /** * Returns the name of the View to be used to identify Views in Transitions. * Names should be unique in the View hierarchy. * * <p>This returns null if the View has not been given a name.</p> * * @return The name used of the View to be used to identify Views in Transitions or null * if no name has been given. */ public static String getTransitionName(View v) { return ViewUtils.getTransitionName(v); } }