android.support.transition.TransitionManagerPort.java Source code

Java tutorial

Introduction

Here is the source code for android.support.transition.TransitionManagerPort.java

Source

/*
 * Copyright (C) 2016 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 android.support.transition;

import android.support.annotation.RestrictTo;
import android.support.v4.util.ArrayMap;
import android.support.v4.view.ViewCompat;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;

import java.lang.ref.WeakReference;
import java.util.ArrayList;

import static android.support.annotation.RestrictTo.Scope.GROUP_ID;

class TransitionManagerPort {
    // TODO: how to handle enter/exit?

    private static final String[] EMPTY_STRINGS = new String[0];

    private static String LOG_TAG = "TransitionManager";

    private static TransitionPort sDefaultTransition = new AutoTransitionPort();

    private static ThreadLocal<WeakReference<ArrayMap<ViewGroup, ArrayList<TransitionPort>>>> sRunningTransitions = new ThreadLocal<>();

    static ArrayList<ViewGroup> sPendingTransitions = new ArrayList<>();

    ArrayMap<ScenePort, TransitionPort> mSceneTransitions = new ArrayMap<>();

    ArrayMap<ScenePort, ArrayMap<ScenePort, TransitionPort>> mScenePairTransitions = new ArrayMap<>();

    ArrayMap<ScenePort, ArrayMap<String, TransitionPort>> mSceneNameTransitions = new ArrayMap<>();

    ArrayMap<String, ArrayMap<ScenePort, TransitionPort>> mNameSceneTransitions = new ArrayMap<>();

    /**
     * Gets the current default transition. The initial value is an {@link
     * AutoTransition} instance.
     *
     * @return The current default transition.
     * @hide pending later changes
     * @see #setDefaultTransition(TransitionPort)
     */
    @RestrictTo(GROUP_ID)
    public static TransitionPort getDefaultTransition() {
        return sDefaultTransition;
    }

    /**
     * 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
     */
    @RestrictTo(GROUP_ID)
    public void setDefaultTransition(TransitionPort transition) {
        sDefaultTransition = transition;
    }

    /**
     * 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(ScenePort scene, TransitionPort transition) {

        final ViewGroup sceneRoot = scene.getSceneRoot();

        TransitionPort transitionClone = null;
        if (transition != null) {
            transitionClone = transition.clone();
            transitionClone.setSceneRoot(sceneRoot);
        }

        ScenePort oldScene = ScenePort.getCurrentScene(sceneRoot);
        if (oldScene != null && oldScene.isCreatedFromLayoutResource()) {
            transitionClone.setCanRemoveViews(true);
        }

        sceneChangeSetup(sceneRoot, transitionClone);

        scene.enter();

        sceneChangeRunTransition(sceneRoot, transitionClone);
    }

    static ArrayMap<ViewGroup, ArrayList<TransitionPort>> getRunningTransitions() {
        WeakReference<ArrayMap<ViewGroup, ArrayList<TransitionPort>>> runningTransitions = sRunningTransitions
                .get();
        if (runningTransitions == null || runningTransitions.get() == null) {
            ArrayMap<ViewGroup, ArrayList<TransitionPort>> transitions = new ArrayMap<>();
            runningTransitions = new WeakReference<>(transitions);
            sRunningTransitions.set(runningTransitions);
        }
        return runningTransitions.get();
    }

    private static void sceneChangeRunTransition(final ViewGroup sceneRoot, final TransitionPort transition) {
        if (transition != null && sceneRoot != null) {
            MultiListener listener = new MultiListener(transition, sceneRoot);
            sceneRoot.addOnAttachStateChangeListener(listener);
            sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
        }
    }

    private static void sceneChangeSetup(ViewGroup sceneRoot, TransitionPort transition) {

        // Capture current values
        ArrayList<TransitionPort> runningTransitions = getRunningTransitions().get(sceneRoot);

        if (runningTransitions != null && runningTransitions.size() > 0) {
            for (TransitionPort runningTransition : runningTransitions) {
                runningTransition.pause(sceneRoot);
            }
        }

        if (transition != null) {
            transition.captureValues(sceneRoot, true);
        }

        // Notify previous scene that it is being exited
        ScenePort previousScene = ScenePort.getCurrentScene(sceneRoot);
        if (previousScene != null) {
            previousScene.exit();
        }
    }

    public static void go(ScenePort scene) {
        changeScene(scene, sDefaultTransition);
    }

    public static void go(ScenePort scene, TransitionPort transition) {
        changeScene(scene, transition);
    }

    public static void beginDelayedTransition(final ViewGroup sceneRoot) {
        beginDelayedTransition(sceneRoot, null);
    }

    public static void beginDelayedTransition(final ViewGroup sceneRoot, TransitionPort transition) {
        if (!sPendingTransitions.contains(sceneRoot) && ViewCompat.isLaidOut(sceneRoot)) {
            if (TransitionPort.DBG) {
                Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " + sceneRoot + ", " + transition);
            }
            sPendingTransitions.add(sceneRoot);
            if (transition == null) {
                transition = sDefaultTransition;
            }
            final TransitionPort transitionClone = transition.clone();
            sceneChangeSetup(sceneRoot, transitionClone);
            ScenePort.setCurrentScene(sceneRoot, null);
            sceneChangeRunTransition(sceneRoot, transitionClone);
        }
    }

    public void setTransition(ScenePort scene, TransitionPort transition) {
        mSceneTransitions.put(scene, transition);
    }

    public void setTransition(ScenePort fromScene, ScenePort toScene, TransitionPort transition) {
        ArrayMap<ScenePort, TransitionPort> sceneTransitionMap = mScenePairTransitions.get(toScene);
        if (sceneTransitionMap == null) {
            sceneTransitionMap = new ArrayMap<>();
            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 ScenePort#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 TransitionPort getTransition(ScenePort scene) {
        TransitionPort transition;
        ViewGroup sceneRoot = scene.getSceneRoot();
        if (sceneRoot != null) {
            // TODO: cached in Scene instead? long-term, cache in View itself
            ScenePort currScene = ScenePort.getCurrentScene(sceneRoot);
            if (currScene != null) {
                ArrayMap<ScenePort, TransitionPort> 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;
    }

    /**
     * Retrieve the transition from a named scene to a target defined scene if one has been
     * associated with this TransitionManager.
     *
     * <p>A named scene is an indirect link for a transition. Fundamentally a named
     * scene represents a potentially arbitrary intersection point of two otherwise independent
     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
     * In this way applications may define an API for more sophisticated transitions between
     * caller and called activities very similar to the way that <code>Intent</code> extras
     * define APIs for arguments and data propagation between activities.</p>
     *
     * @param fromName Named scene that this transition corresponds to
     * @param toScene  Target scene that this transition will move to
     * @return Transition corresponding to the given fromName and toScene or null
     * if no association exists in this TransitionManager
     * @see #setTransition(String, ScenePort, TransitionPort)
     */
    public TransitionPort getNamedTransition(String fromName, ScenePort toScene) {
        ArrayMap<ScenePort, TransitionPort> m = mNameSceneTransitions.get(fromName);
        if (m != null) {
            return m.get(toScene);
        }
        return null;
    }

    ;

    /**
     * Retrieve the transition from a defined scene to a target named scene if one has been
     * associated with this TransitionManager.
     *
     * <p>A named scene is an indirect link for a transition. Fundamentally a named
     * scene represents a potentially arbitrary intersection point of two otherwise independent
     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
     * In this way applications may define an API for more sophisticated transitions between
     * caller and called activities very similar to the way that <code>Intent</code> extras
     * define APIs for arguments and data propagation between activities.</p>
     *
     * @param fromScene Scene that this transition starts from
     * @param toName    Name of the target scene
     * @return Transition corresponding to the given fromScene and toName or null
     * if no association exists in this TransitionManager
     */
    public TransitionPort getNamedTransition(ScenePort fromScene, String toName) {
        ArrayMap<String, TransitionPort> m = mSceneNameTransitions.get(fromScene);
        if (m != null) {
            return m.get(toName);
        }
        return null;
    }

    /**
     * Retrieve the supported target named scenes when transitioning away from the given scene.
     *
     * <p>A named scene is an indirect link for a transition. Fundamentally a named
     * scene represents a potentially arbitrary intersection point of two otherwise independent
     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
     * In this way applications may define an API for more sophisticated transitions between
     * caller and called activities very similar to the way that <code>Intent</code> extras
     * define APIs for arguments and data propagation between activities.</p>
     *
     * @param fromScene Scene to transition from
     * @return An array of Strings naming each supported transition starting from
     * <code>fromScene</code>. If no transitions to a named scene from the given
     * scene are supported this function will return a String[] of length 0.
     * @see #setTransition(ScenePort, String, TransitionPort)
     */
    public String[] getTargetSceneNames(ScenePort fromScene) {
        final ArrayMap<String, TransitionPort> m = mSceneNameTransitions.get(fromScene);
        if (m == null) {
            return EMPTY_STRINGS;
        }
        final int count = m.size();
        final String[] result = new String[count];
        for (int i = 0; i < count; i++) {
            result[i] = m.keyAt(i);
        }
        return result;
    }

    /**
     * Set a transition from a specific scene to a named scene.
     *
     * <p>A named scene is an indirect link for a transition. Fundamentally a named
     * scene represents a potentially arbitrary intersection point of two otherwise independent
     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
     * In this way applications may define an API for more sophisticated transitions between
     * caller and called activities very similar to the way that <code>Intent</code> extras
     * define APIs for arguments and data propagation between activities.</p>
     *
     * @param fromScene  Scene to transition from
     * @param toName     Named scene to transition to
     * @param transition Transition to use
     * @see #getTargetSceneNames(ScenePort)
     */
    public void setTransition(ScenePort fromScene, String toName, TransitionPort transition) {
        ArrayMap<String, TransitionPort> m = mSceneNameTransitions.get(fromScene);
        if (m == null) {
            m = new ArrayMap<>();
            mSceneNameTransitions.put(fromScene, m);
        }
        m.put(toName, transition);
    }

    /**
     * Set a transition from a named scene to a concrete scene.
     *
     * <p>A named scene is an indirect link for a transition. Fundamentally a named
     * scene represents a potentially arbitrary intersection point of two otherwise independent
     * transitions. Activity A may define a transition from scene X to "com.example.scene.FOO"
     * while activity B may define a transition from scene "com.example.scene.FOO" to scene Y.
     * In this way applications may define an API for more sophisticated transitions between
     * caller and called activities very similar to the way that <code>Intent</code> extras
     * define APIs for arguments and data propagation between activities.</p>
     *
     * @param fromName   Named scene to transition from
     * @param toScene    Scene to transition to
     * @param transition Transition to use
     * @see #getNamedTransition(String, ScenePort)
     */
    public void setTransition(String fromName, ScenePort toScene, TransitionPort transition) {
        ArrayMap<ScenePort, TransitionPort> m = mNameSceneTransitions.get(fromName);
        if (m == null) {
            m = new ArrayMap<>();
            mNameSceneTransitions.put(fromName, m);
        }
        m.put(toScene, transition);
    }

    public void transitionTo(ScenePort scene) {
        // Auto transition if there is no transition declared for the Scene, but there is
        // a root or parent view
        changeScene(scene, getTransition(scene));
    }

    /**
     * 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.
     */
    private static class MultiListener
            implements ViewTreeObserver.OnPreDrawListener, View.OnAttachStateChangeListener {

        TransitionPort mTransition;

        ViewGroup mSceneRoot;

        MultiListener(TransitionPort 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<TransitionPort> runningTransitions = getRunningTransitions().get(mSceneRoot);
            if (runningTransitions != null && runningTransitions.size() > 0) {
                for (TransitionPort runningTransition : runningTransitions) {
                    runningTransition.resume(mSceneRoot);
                }
            }
            mTransition.clearValues(true);
        }

        @Override
        public boolean onPreDraw() {
            removeListeners();
            sPendingTransitions.remove(mSceneRoot);
            // Add to running list, handle end to remove it
            final ArrayMap<ViewGroup, ArrayList<TransitionPort>> runningTransitions = getRunningTransitions();
            ArrayList<TransitionPort> currentTransitions = runningTransitions.get(mSceneRoot);
            ArrayList<TransitionPort> previousRunningTransitions = null;
            if (currentTransitions == null) {
                currentTransitions = new ArrayList<>();
                runningTransitions.put(mSceneRoot, currentTransitions);
            } else if (currentTransitions.size() > 0) {
                previousRunningTransitions = new ArrayList<>(currentTransitions);
            }
            currentTransitions.add(mTransition);
            mTransition.addListener(new TransitionPort.TransitionListenerAdapter() {
                @Override
                public void onTransitionEnd(TransitionPort transition) {
                    ArrayList<TransitionPort> currentTransitions = runningTransitions.get(mSceneRoot);
                    currentTransitions.remove(transition);
                }
            });
            mTransition.captureValues(mSceneRoot, false);
            if (previousRunningTransitions != null) {
                for (TransitionPort runningTransition : previousRunningTransitions) {
                    runningTransition.resume(mSceneRoot);
                }
            }
            mTransition.playTransition(mSceneRoot);

            return true;
        }
    }
}