走进源码之Transition

简介

看下官方文档对于 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
祝好
共勉
不喜勿喷

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267