走进源码之Transition

作者: Wang_Guan | 来源:发表于2020-05-06 17:13 被阅读0次

    简介

    看下官方文档对于 Transition 的介绍:

    A Transition holds information about animations that will be run on its targets during a scene change. Subclasses of this abstract class may choreograph several child transitions (TransitionSet )or they may perform custom animations themselves. Any Transition has two main jobs: (1) capture property values, and (2) play animations based on changes to captured property values. A custom transition knows what property values on View objects are of interest to it, and also knows how to animate changes to those values. For example, the Fade transition tracks changes to visibility-related properties and is able to construct and run animations that fade items in or out based on changes to those properties.

    文档大致意思是说:
    Transiton 会持有有关场景变化期间将作用在它的目标上的动画信息。Transiton 是个抽象类,它的子类可以编排好几个 transition,或者它们可以自己执行自定义动画。所有的 transition 都有两个主要任务:(1)捕获属性值 (2)基于属性值的变化执行动画。自定义 transition 明白 view 上的哪些属性是自己感兴趣的,并且知道当这些属性改变的时候需要执行怎样的动画。举例来说,Fade transition 跟踪与可见性相关属性的更改,然后基于这些属性的变化构造出相应的动画并执行。

    由上可以知道,transition 主要的任务就是确定自己需要跟踪的属性值,并且根据这些属性值的改变,执行相应的动画。

    ⚠️WARNING:因为 SurfaceView 和 TextureView 的特殊性,官方不建议在这两个控件上使用 transition。详情见下:

    Note: Transitions may not work correctly with either SurfaceView or TextureView, due to the way that these views are displayed on the screen. For SurfaceView, the problem is that the view is updated from a non-UI thread, so changes to the view due to transitions (such as moving and resizing the view) may be out of sync with the display inside those bounds. TextureView is more compatible with transitions in general, but some specific transitions (such as Fade) may not be compatible with TextureView because they rely on ViewOverlay functionality, which does not currently work with TextureView.

    入口

    要知道 transition 是如何工作的,或许我们应该从顶层调用入手,看一个完整的流程是如何走通的。

    fab.setOnClickListener {
                TransitionManager.beginDelayedTransition(container, transformExpand)
                fab.visibility = View.GONE
                content.visibility = View.VISIBLE
            }
    

    天呢,执行一个 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);
                // 不设置 transition 会自动赋值默认 transition
                // 默认 transition 是 AutoTransition(Fade,ChangeBounds)
                if (transition == null) {
                    transition = sDefaultTransition;
                }
                final Transition transitionClone = transition.clone();
                //   记录 start scene 的状态
                sceneChangeSetup(sceneRoot, transitionClone);
                //   设置当前 scene
                Scene.setCurrentScene(sceneRoot, null);
                //   根据两个场景变化执行设置的 transition
                sceneChangeRunTransition(sceneRoot, transitionClone);
            }
        }
    

    从👆看到,transition 变化分为三步:
    step1:记录 start scene 的状态
    step2:设置当前 scene
    step3:根据前后状态的改变创建动画并运行

    跟进去康康 sceneChangeSetup 做了什么

        private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {
    
            // Capture current values
            ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);
    
            //  暂停在 sceneRoot 上已运行的动画
            if (runningTransitions != null && runningTransitions.size() > 0) {
                for (Transition runningTransition : runningTransitions) {
                    runningTransition.pause(sceneRoot);
                }
            }
    
            // 捕获当前 scene 的属性值
            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();
            }
        }
    

    这里,我们看到了 transition 两个主要任务中的一个,那就是捕获当前的属性

    transition.captureValues(sceneRoot, true);
    

    看下里面做了什么

    start 参数用来判断是 start scene 还是 end scene
    void captureValues(ViewGroup sceneRoot, boolean start) {
            clearValues(start);
            //  根据已设置的 id、name、type 找到影响的 view 捕获相关属性
            if ((mTargetIds.size() > 0 || mTargets.size() > 0)
                    && (mTargetNames == null || mTargetNames.isEmpty())
                    && (mTargetTypes == null || mTargetTypes.isEmpty())) {
                for (int i = 0; i < mTargetIds.size(); ++i) {
                    int id = mTargetIds.get(i);
                    View view = sceneRoot.findViewById(id);
                    if (view != null) {
                        TransitionValues values = new TransitionValues();
                        values.view = view;
                        if (start) {
                            captureStartValues(values);
                        } else {
                            captureEndValues(values);
                        }
                        values.targetedTransitions.add(this);
                        capturePropagationValues(values);
                        if (start) {
                            addViewValues(mStartValues, view, values);
                        } else {
                            addViewValues(mEndValues, view, values);
                        }
                    }
                }
                for (int i = 0; i < mTargets.size(); ++i) {
                    View view = mTargets.get(i);
                    TransitionValues values = new TransitionValues();
                    values.view = view;
                    if (start) {
                        captureStartValues(values);
                    } else {
                        captureEndValues(values);
                    }
                    values.targetedTransitions.add(this);
                    capturePropagationValues(values);
                    if (start) {
                        addViewValues(mStartValues, view, values);
                    } else {
                        addViewValues(mEndValues, view, values);
                    }
                }
            } else {
                // 捕获整个 view tree
                captureHierarchy(sceneRoot, start);
            }
            if (!start && mNameOverrides != null) {
                int numOverrides = mNameOverrides.size();
                ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides);
                for (int i = 0; i < numOverrides; i++) {
                    String fromName = mNameOverrides.keyAt(i);
                    overriddenViews.add(mStartValues.nameValues.remove(fromName));
                }
                for (int i = 0; i < numOverrides; i++) {
                    View view = overriddenViews.get(i);
                    if (view != null) {
                        String toName = mNameOverrides.valueAt(i);
                        mStartValues.nameValues.put(toName, view);
                    }
                }
            }
        }
    

    captureStartValuescaptureEndValues 是两个抽象函数,我们需要在子类中实现,设置自己想要捕获的属性值。

    OK,现在反过头接着看 step2,这一步只是把当前 scene 置 null

    static void setCurrentScene(View view, Scene scene) {
            view.setTagInternal(com.android.internal.R.id.current_scene, scene);
        }
    

    接着看 step3

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

    欸~这里新建了一个 MultiListener ,然后注册了 AttachStateChangeListenerPreDrawListener,康康 MultiListener 里面的具体实现。

    private static class MultiListener implements ViewTreeObserver.OnPreDrawListener,
                View.OnAttachStateChangeListener {
    
            Transition mTransition;
            ViewGroup mSceneRoot;
            final ViewTreeObserver mViewTreeObserver;
    
            MultiListener(Transition transition, ViewGroup sceneRoot) {
                mTransition = transition;
                mSceneRoot = sceneRoot;
                mViewTreeObserver = mSceneRoot.getViewTreeObserver();
            }
    
            private void removeListeners() {
                if (mViewTreeObserver.isAlive()) {
                    mViewTreeObserver.removeOnPreDrawListener(this);
                } else {
                    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);
            }
    
            @Override
            public boolean onPreDraw() {
                removeListeners();
    
                // Don't start the transition if it's no longer pending.
                // 当前 scene 没有预备 transition,后续不再执行。
                if (!sPendingTransitions.remove(mSceneRoot)) {
                    return true;
                }
    
                // Add to running list, handle end to remove it
                final ArrayMap<ViewGroup, ArrayList<Transition>> runningTransitions =
                        getRunningTransitions();
                ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
                ArrayList<Transition> previousRunningTransitions = null;
                if (currentTransitions == null) {
                    currentTransitions = new ArrayList<Transition>();
                    runningTransitions.put(mSceneRoot, currentTransitions);
                } else if (currentTransitions.size() > 0) {
                    previousRunningTransitions = new ArrayList<Transition>(currentTransitions);
                }
                currentTransitions.add(mTransition);
                mTransition.addListener(new TransitionListenerAdapter() {
                    @Override
                    public void onTransitionEnd(Transition transition) {
                        // 移除已经执行完的 transition
                        ArrayList<Transition> currentTransitions =
                                runningTransitions.get(mSceneRoot);
                        currentTransitions.remove(transition);
                    }
                });
                // 捕获当前状态,这里第二个参数为 false,结合前面说的,这里其实是捕获 end 状态了
                mTransition.captureValues(mSceneRoot, false);
                if (previousRunningTransitions != null) {
                    for (Transition runningTransition : previousRunningTransitions) {
                        runningTransition.resume(mSceneRoot);
                    }
                }
                // 执行 transition
                mTransition.playTransition(mSceneRoot);
    
                return true;
            }
        };
    

    小结下这段代码,这段代码着重在函数 onViewDetachedFromWindow 以及函数 onPreDrawonPreDraw 是 transition 执行的触发器,非常的重要。而 onViewDetachedFromWindow 也非常的重要,当视图被移除的时候,该函数就被回调且早于onPreDraw,这样就能有效控制 transition 的执行,这里面主要做一些清除恢复的工作。

    Transition.playTransition(ViewGroup sceneRoot)

    前面主要都是做一些准备工作,到这里,终于看到 play 的字眼了,康康是如何 play 的。

    void playTransition(ViewGroup sceneRoot) {
            // 存储前后 values 的 list
            mStartValuesList = new ArrayList<TransitionValues>();
            mEndValuesList = new ArrayList<TransitionValues>();
            // 对前后捕获的 value 进行匹配装载,讲符合条件的装到 valuesList 中。
            matchStartAndEnd(mStartValues, mEndValues);
    
            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
            int numOldAnims = runningAnimators.size();
            WindowId windowId = sceneRoot.getWindowId();
            for (int i = numOldAnims - 1; i >= 0; i--) {
                Animator anim = runningAnimators.keyAt(i);
                if (anim != null) {
                    AnimationInfo oldInfo = runningAnimators.get(anim);
                    if (oldInfo != null && oldInfo.view != null && oldInfo.windowId == windowId) {
                        TransitionValues oldValues = oldInfo.values;
                        View oldView = oldInfo.view;
                        TransitionValues startValues = getTransitionValues(oldView, true);
                        TransitionValues endValues = getMatchedTransitionValues(oldView, true);
                        if (startValues == null && endValues == null) {
                            endValues = mEndValues.viewValues.get(oldView);
                        }
                        boolean cancel = (startValues != null || endValues != null) &&
                                oldInfo.transition.isTransitionRequired(oldValues, endValues);
                        if (cancel) {
                            if (anim.isRunning() || anim.isStarted()) {
                                if (DBG) {
                                    Log.d(LOG_TAG, "Canceling anim " + anim);
                                }
                                anim.cancel();
                            } else {
                                if (DBG) {
                                    Log.d(LOG_TAG, "removing anim from info list: " + anim);
                                }
                                runningAnimators.remove(anim);
                            }
                        }
                    }
                }
            }
    
            //  根据相关参数创建动画
            createAnimators(sceneRoot, mStartValues, mEndValues, mStartValuesList, mEndValuesList);
            // 执行动画
            runAnimators();
        }
    

    前面一大段代码似乎也没真正的 play,而且为创建 animators 做准备,创建完 animators 后才是真正执行动画。

    protected void createAnimators(ViewGroup sceneRoot, TransitionValuesMaps startValues,
                TransitionValuesMaps endValues, ArrayList<TransitionValues> startValuesList,
                ArrayList<TransitionValues> endValuesList) {
            if (DBG) {
                Log.d(LOG_TAG, "createAnimators() for " + this);
            }
            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
            long minStartDelay = Long.MAX_VALUE;
            int minAnimator = mAnimators.size();
            SparseLongArray startDelays = new SparseLongArray();
            int startValuesListCount = startValuesList.size();
            for (int i = 0; i < startValuesListCount; ++i) {
                TransitionValues start = startValuesList.get(i);
                TransitionValues end = endValuesList.get(i);
                ...
                // Only bother trying to animate with values that differ between start/end
                boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
                if (isChanged) {
                    ...
                    // TODO: what to do about targetIds and itemIds?
                    // 实现自定义的 transition 可实现自己的动画
                    Animator animator = createAnimator(sceneRoot, start, end);
                    if (animator != null) {
                        // Save animation info for future cancellation purposes
                        ...
                       if (animator != null) {
                            // 延时处理
                            if (mPropagation != null) {
                                long delay = mPropagation
                                        .getStartDelay(sceneRoot, this, start, end);
                                startDelays.put(mAnimators.size(), delay);
                                minStartDelay = Math.min(delay, minStartDelay);
                            }
                            AnimationInfo info = new AnimationInfo(view, getName(), this,
                                    sceneRoot.getWindowId(), infoValues);
                            runningAnimators.put(animator, info);
                            // 加入待执行的列表中
                            mAnimators.add(animator);
                        }
                    }
                }
            }
            // 设置动画延时播放
            if (startDelays.size() != 0) {
                for (int i = 0; i < startDelays.size(); i++) {
                    int index = startDelays.keyAt(i);
                    Animator animator = mAnimators.get(index);
                    long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
                    animator.setStartDelay(delay);
                }
            }
        }
    

    OK,这里,我们的动画就创建好了,并且将待执行的动画都放进 mAnimators 里面了,接着看动画是如何执行的。

        protected void runAnimators() {
            ...
            start();
            ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
            // Now start every Animator that was previously created for this transition
            for (Animator anim : mAnimators) {
                ...
                if (runningAnimators.containsKey(anim)) {
                    start();
                    runAnimator(anim, runningAnimators);
                }
            }
            mAnimators.clear();
            end();
        }
    

    这里遍历了 mAnimators ,然后执行动画,but,怎么没看到 animator.start() 呢?😂

    private void runAnimator(Animator animator,
                final ArrayMap<Animator, AnimationInfo> runningAnimators) {
            if (animator != null) {
                // TODO: could be a single listener instance for all of them since it uses the param
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationStart(Animator animation) {
                        mCurrentAnimators.add(animation);
                    }
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        runningAnimators.remove(animation);
                        mCurrentAnimators.remove(animation);
                    }
                });
                animate(animator);
            }
        }
    

    runAnimator 监听了动画的开始和结束,接着又调用了animate ,这个应该就执行了吧!🤣

        protected void animate(Animator animator) {
            // TODO: maybe pass auto-end as a boolean parameter?
            if (animator == null) {
                end();
            } else {
                if (getDuration() >= 0) {
                    animator.setDuration(getDuration());
                }
                if (getStartDelay() >= 0) {
                    animator.setStartDelay(getStartDelay() + animator.getStartDelay());
                }
                if (getInterpolator() != null) {
                    animator.setInterpolator(getInterpolator());
                }
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        end();
                        animation.removeListener(this);
                    }
                });
                animator.start();
            }
        }
    

    恶魔妈妈买面膜👿,终于看到 animator.start() 了,这里,动画就真正的执行起来了。动画执行之前,我们看到这里还对 animator 做了一些统一配置,这里主要是针对 TransitionSet 的,我们用 TransitionSet 组合多个 Transition 的时候,可以统一配置 duration、startDelay、interpolator 的属性。

    小结一下,这一 part 就是动画创建与执行的地方了。先对 mStartValues 和 mEndValues 进行匹配装载,装配符合条件的 values 到 mStartValuesList 和 mEndValuesList 中,然后调用 createAnimator ,根据传入的 mStartValues 、mEndValues 、mStartValuesList 、mEndValuesList 找出有变化的属性,然后创建动画,并将动画存储到 mAnimators 中,最后调用 runAnimators 遍历 mAnimators ,逐个执行动画。

    总结

    通过 Android 给我们提供的 TransitionManager 来操作 Transition 是一个不错的选择。 TransitionManager 顾名思义,就是个管理者,它将 View 和 Transition 做了关系连结。
    Transition 变化主要分为三步完成:

    1. 记录 start scene 的状态,
    2. 设置当前的 scene,
    3. 根据前后状态的改变创建动画并且运行。

    在调用处我们发现,并没有任何类似于 TransitionManager.start() 的相关调用,这是因为,transition 的驱动是靠 onPreDraw() 回调来完成的。

    Last

    Github
    祝好
    共勉
    不喜勿喷

    相关文章

      网友评论

        本文标题:走进源码之Transition

        本文链接:https://www.haomeiwen.com/subject/qgrightx.html