Transition 调用方法分析

本文详细解析了TransitionManager的主要方法,包括transitionTo、go和beginDelayedTransition的使用方式与内部实现机制。探讨了这些方法如何捕获场景变化的起始与结束值,并通过监听器触发Transition动画。
摘要由CSDN通过智能技术生成

Transition 调用方法分析

TransitionManager.transitionTo(Scene)

    /**
     * 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));
    }

注释翻译:变换到给定的场景,使用为这个场景变换指定的合适的Transition. (如果已经在TransitionManger中设置了, 或者使用默认的) .

这里需要着重看括号中的部分,从其中我们可以猜到,实际上,调用TransitionManager.setTransition(就是为Scene变换设置一个Transition, 如果我们之前scene设置了一个Transition, 那么我们使用的是我们设置的Transition, 否则就是使用默认的AutoTransition. 其实,将setTransition改为putTransition更为合适。

这个方法直接调用了changeScene(Scene, Transition)方法,传入其中的Transition使用了getTransition方法, 在getTransition方法中,获取为Scene指定的Transition, 如果没有指定,则使用默认的AutoTransition . 那么如何为Scene指定Transition呢? 有两个方法setTransition(Scene scene, Transition transition)setTransition(Scene fromScene, Scene toScene, Transition transition), 其实将setTransition改为putTransition更为合适。

    /**
     * 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);
    }

注释翻译:设置一个,当进入给定场景时,发生的特定变换。
参数:scene, 给定的场景,transition, 和scene绑定在一起的,当进入scene时,使用这个transition, 如果为null, 则使用默认的AutoTransition .

    /**
     * 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);
    }

注释翻译:设置一个,当给定的进入退出场景切换时,发生的特定变换。这里实际上就是设置了两个场景,当从场景1切换到场景2时,使用指定的Transition .
参数翻译:fromScene, 发生切换的退出场景;toScene, 发生切换的进入场景;transition, 从fromScene切换到toScene时,使用的Transition, 如果为null, 则使用默认的AutoTransition .

对于源码的操作,其实很简单,使用了ArrayMap将Scene和Transition绑定,而两个Scene和一个Transition绑定,这里也是使用了两层的ArrayMap, 细节可以结合getTransition(Scene)的源码进行查看。

接下来继续说TransitionManager.transitionTo(Scene)

TransitionManager.transitionTo(Scene)中直接调用了changeScene(Scene scene, Transition 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(Scene scene, Transition transition) {

        final ViewGroup sceneRoot = scene.getSceneRoot();
        if (!sPendingTransitions.contains(sceneRoot)) {
            sPendingTransitions.add(sceneRoot);

            Transition transitionClone = null;
            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);
        }
    }

注释翻译:这里是所有场景变换工作进行的地方。在这个方法中,为给定的Transition捕获起始值,退出当前场景,进入新场景,为给定的Transition捕获结束值,最后进行值切换动画。

参数翻译:scene, 进入的新场景;transition, 这个场景改变时的变换。

从注释中,我们可以看出,所有的场景切换工作都是在这个方法中发生的。所以着重分析一下:

private static void changeScene(Scene scene, Transition transition) {

    // 获取场景切换发生的sceneRoot
    final ViewGroup sceneRoot = scene.getSceneRoot();
    // 如果这个sceneRoot没有在进行场景切换,则进行下面的工作,如果正在进行场景切换,则忽略
    if (!sPendingTransitions.contains(sceneRoot)) {
        // 添加到切换队列中,表示这个sceneRoot正在进行场景切换
        sPendingTransitions.add(sceneRoot);

        // 准备Transition, 这里使用了Transition.clone, 目的好像是为了防止内存泄露
        // 因为Transition可能会被用户一直保留
        Transition transitionClone = null;
        if (transition != null) {
            transitionClone = transition.clone();
            transitionClone.setSceneRoot(sceneRoot);
        }

        // 如果这个sceneRoot当前有一个场景,那么需要一些设置(暂时不清楚这个设置有什么作用)
        Scene oldScene = Scene.getCurrentScene(sceneRoot);
        if (oldScene != null && transitionClone != null &&
                oldScene.isCreatedFromLayoutResource()) {
            transitionClone.setCanRemoveViews(true);
        }

        // 为给定的Transition捕获起始值
        sceneChangeSetup(sceneRoot, transitionClone);

        // 退出当前场景,进入新场景
        scene.enter();

        // 为给定的Transition不过结束值,并进行场景切换动画
        sceneChangeRunTransition(sceneRoot, transitionClone);
    }
}

在这个方法中,调用了关键的sceneChangeSetup(ViewGroup sceneRoot, Transition transition)sceneChangeRunTransition(final ViewGroup sceneRoot, final Transition transition)方法。

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

        // 在进行场景切换时,可能当前的sceneRoot正在进行
        // 如果这个sceneRoot正在进行场景切换,则暂停当前的切换
        // Capture current values
        ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);

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

        // Transition捕获sceneRoot的起始值
        if (transition != null) {
            // 第二个参数为true表示起始值,false表示结束值
            transition.captureValues(sceneRoot, true);
        }

        // 如果正在进行场景切换,那么执行退出操作,实际上也只是触发退出事件
        // Notify previous scene that it is being exited
        Scene previousScene = Scene.getCurrentScene(sceneRoot);
        if (previousScene != null) {
            previousScene.exit();
        }
    }
    private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
            final Transition transition) {
        if (transition != null && sceneRoot != null) {
            MultiListener listener = new MultiListener(transition, sceneRoot);
            sceneRoot.addOnAttachStateChangeListener(listener);
            sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
        }
    }

在这个方法中,为sceneRoot添加了一个OnAttachStateChangeListener, 也为sceneRoot的ViewTreeObserver添加了一个OnPreDrawListener, 看起来并没有做什么特别的工作。

可以注意到,这两个监听器实际上都是一个MultiListener, 主要的工作也就是在这个监听器中。

    /**
     * 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 {

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

        /**
         * 首先要明确,这个监听器被调用的时机:是在View onMeasure, onLayout调用之后, onDraw调用之前
         */
        @Override
        public boolean onPreDraw() {
            // 清理工作,让动画只执行一次
            removeListeners();

            // 如果Transition已经从队列中中移除(表明已经结束了), 则不再进行,直接返回。
            // Don't start the transition if it's no longer pending.
            if (!sPendingTransitions.remove(mSceneRoot)) {
                return true;
            }

            // 将需要进行的Transition添加到正在运行的队列中,在Transition结束之后移除
            // Add to running list, handle end to remove it
            // 获取正在运行的所有Transition
            final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
                    getRunningTransitions();
            // 获取当前sceneRoot的所有Transition
            ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
            // 在这个Transition执行之前的时候,运行在sceneRoot上的所有Transition
            ArrayList<Transition> previousRunningTransitions = null;
            // 如果sceneRoot没有在运行Transition, 那么创建一个新的列表用于保存sceneRoot的Transition
            // 并将其添加到正在运行的队列中
            if (currentTransitions == null) {
                currentTransitions = new ArrayList<Transition>();
                runningTransitions.put(mSceneRoot, currentTransitions);
            // 如果这个sceneRoot有正在运行的Transition, 那么暂时存储这些Transition
            } else if (currentTransitions.size() > 0) {
                previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
            }
            // 添加现在的Transition
            currentTransitions.add(mTransition);
            // 为现在的Transition添加监听器,在Transition结束之后,移除现在的Transition
            mTransition.addListener(new Transition.TransitionListenerAdapter() {
                @Override
                public void onTransitionEnd(Transition transition) {
                    // 从sceneRoot正在进行的Transition列表中,移除现在的Transition
                    ArrayList<Transition> currentTransitions =
                            runningTransitions.get(mSceneRoot);
                    currentTransitions.remove(transition);
                }
            });
            // 捕获scene变化的结束值
            mTransition.captureValues(mSceneRoot, false);
            // 如果有之前运行的Transition, 那么继续运行这些Transition
            if (previousRunningTransitions != null) {
                for (Transition runningTransition : previousRunningTransitions) {
                    runningTransition.resume(mSceneRoot);
                }
            }
            // 进行现在的Transition
            mTransition.playTransition(mSceneRoot);

            return true;
        }
    };

注释翻译:这个私有的功能类用于监听OnPreDrawOnAttachStateChange事件。OnPreDraw是我们主要关系的一个,因为它是触发变换发生的地方。OnAttachStateChange事件也很重要,这是为了避免view在OnPreView事件触发之前被移除;也被用于在OnPreDraw监听器没有被即是调用时的清理工作。

上面的代码解释非常多,实际上也只是那么几个概念和操作:
- Android将sceneRoot和在其上运行的Transition列表作为一个键值对放在一个ArrayMap中,而这个ArrayMap是被一个WeakReference引用,所以不必担心内存泄露,造成sceneRoot没有被释放,从而引起Activity泄露的情况
- 一般情况而言,一个sceneRoot之前也没有正在运行的Transition, 所以很自然的,我们可以从上面的列表中删除一些代码来看第一次运行Transition的样子是怎样的。
- 为Transition添加一个监听器,用于监听在Transition结束之后,从与sceneRoot绑定的Transition列表中移除这个Transition, 避免内存泄露
- 捕获scene的结束值
- 结合之前,Transition已经捕获了scene的起始值,现在就使用Transition.playTransition(ViewGroup sceneRoot)方法进行Transition .
- 当然这里考虑的更多,一是可能sceneRoot上有正在运行的Transition, 而且可能不止一个,二是在进行这些操作的时候,原来正在运行的Transition好像是要被暂停,所以这里需要重新唤醒所有值钱正在运行的Transition .

TransitionManager.go(Scene, Transition)

这个方法有一个重载方法TransitionManager.go(Scene), 在这个方法中,默认使用AutoTransition .

在这个方法中,也只是直接调用了TransitionManager.changeScene(Scene, Transition)方法,上面已经分析过了,这里就不再说了。这里主要是对比TransitionManager.go(Scene, Transition)TransitionManager.transitionTo(Scene)有什么不同。

其实并没有太多的不同,使用TransitionManager.transitionTo(Scene)更多的是在xml中进行配置,然后进行inflate, 而在xml中配置的方式比较方便,能够将一些复杂的多个Transition组合比较方便的实现,同时也可以配置场景之间的切换。而TransitionManager.go(Scene, Transition)则在使用起来更为简单,对于一些简单的只是做场景变换,并不存在过多的Scene与Scene之间的互动更加方便,但是如果需要添加更多的操作,也可以在Transition上进行更多的配置,在xml中定义的操作,在代码中也可以实现,只是这样的实现比较麻烦。

beginDelayedTransition(final ViewGroup sceneRoot, Transition transition)

如果并不是需要很多的场景切换互动,其实最多的就是使用这个方法。这个方法也比较特别,因为使用这个方法的是先调用这个方法,然后对sceneRoot进行变换就可以了,下面分析一下源码。

    /**
     * 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>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>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) && sceneRoot.isLaidOut()) {
            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);
        }
    }

注释翻译:这是一个方便的方法用于 捕获给定的sceneRoot在调用这个方法时的状态值和下一次渲染的状态值之间的变化,使用这个变化来进行变换。调用这个方法致使TransitionManager捕获sceneRoot的当前值,并且post一个在下一次渲染时进行Transition的请求。在下一次渲染时,sceneRoot的新的值将被捕获,并且根据原来的值来进行动画。通过这个方法,没有必要创建一个Scene, 取而代之的是在进行下一次需要触发渲染操作之前调用这个方法即可,在调用下一次触发渲染操作的时候就会触发这个方法。

注:多次调用这个方法,只有第一次调用有效。

另外,如果传递一个null的Transition, 那么就会使用默认的AutoTransition .

参数翻译:sceneRoot 进行Transition的View层次的根; transition, 用于sceneRoot发生改变时进行的Transition .

这个方法不同于之前两个方法在于,前两个方法都直接或间接的使用Scene的概念,而且看起来都是直接触发Scene的改变,所以看起来很合理,然而在这个方法中,并没有使用Scene的概念,并且触发Scene的改变并不是调用这个方法,而是在后面的对sceneRoot的操作,致使sceneRoot的渲染,从而再触发Transition. 这在表面上看起来比较玄乎。

其实,需要注意的反而是上面两个方法,上面两个方法使用了Handler机制的概念。触发Transition的操作并不是直接使用这两个方法,而是Handler的下一次post. 具体是怎样的呢,下面分析。

Transition的触发时机

先直接说结论:Transition被触发在于sceneRoot的view层次发生改变,从而onPreView被触发,才直接调用了Transition.playTransition(ViewGroup sceneRoot).

TransitionManager.transitionToTransitionManager.go一样,都是调用了TransitionManager.changeScene方法,在这个方法中,流程如下
1. 捕获起始值
2. 改变sceneRoot, 会触发渲染操作
3. 添加监听器,当渲染时触发监听器,在监听器中捕获结束值,并进行Transition
这个流程看起来好像有点问题,因为第2步先触发渲染操作,第三步再添加监听器。但是这是没有问题的,这是因为,触发渲染时,使用的Handler进行post到主线程进行,而这个过程是要在主线程其他的代码执行完成之后才会触发。所以实际上是在第3步完成之后,渲染操作才会进行。

TransitionManager.beginDelayedTransition则没有调用TransitionManager.changeScene, 他的流程如下
1. 捕获起始值
2. 添加监听器,当渲染时触发监听器,在监听器中捕获结束值,并进行Transition
而在使用这个方法之后,会调用能够触发sceneRoot进行动画的操作。

这个看起来就比较合理了。

总结

这里分析了使用Transition的几个方法,浅层次的分析了TransitionManager的使用。

对于Transition的触发时机的分析,View渲染是否使用了Handler.post, 现在也只是猜测,感觉应该是这样,需要进一步分析。这个之后再说。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值