Android Transition Framework 源码分析

我真是要吐槽下 CSDN 网站,我在家里编辑了草稿发布了文章,在公司点写文章的时候,为何你TM还有我的草稿?搞得我删完草稿,重新发布了一篇新文章,原文章就被覆盖了,我心里真是一群草泥马奔腾而过。还好我在掘金网站投稿,他们帮我备份了,否则我TM的花了几天功夫写的文章就这样没了。
还有,网页各种B广告,真是TM的恶心。

概要

在 Android 4.4 的时候,引入了 Transition Framework ,其原理是利用属性动画,来实现 View 在不同布局的转换效果。

在 Android 5.0 的时候 ,基于 Transition Framework 又引入了 Activity/Fragment Transition 和 Shared Elements Transition。

刚开始用 Transition Framework 的时候,用官网上一些例子试了下,感觉好简单,但是当我在实际中开始使用的时候,却发现效果不尽如人意,那么,从长远的目光看,我觉得还是有必要来真正的了解下 Transition Framework 的原理。

例子

既然是探究原理,所以本文并不会去详细介绍过渡动画如何使用,所以只以一个小例子做引,然后一步一步探究源码,首先看一个效果图

这里写图片描述

首先,从第一印象看,好像不是属性动画,但是如果再想想如何用属性动画实现的时候,却似乎有点棘手。 然而,这个效果用过渡动画来实现,却非常的简单。

先看几个布局,首先是 Activity 的布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scene_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/scene1"/>

</RelativeLayout>

然后看看 scene1.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/transition_fab"
        android:onClick="onFabClick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentEnd="true"
        android:layout_marginBottom="15dp"
        android:layout_marginRight="15dp"
        android:src="@android:drawable/ic_dialog_email"
        app:fabSize="normal"/>

</RelativeLayout>

最后就是 scene2.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/scene2_image"
        android:layout_width="match_parent"
        android:layout_height="300dp"
        android:background="@color/colorPrimary"/>

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/transition_fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/scene2_image"
        android:layout_alignEnd="@id/scene2_image"
        android:layout_marginBottom="-26dp"
        android:layout_marginEnd="15dp"
        android:onClick="onFabClick"
        android:src="@android:drawable/ic_dialog_email"
        app:fabSize="normal"/>

</RelativeLayout>

Activity 主要代码如下

public class MainActivity extends AppCompatActivity {

    private Scene mScene1;
    private Scene mScene2;
    private boolean mIsBack;
    private Transition mChangeBounds = new ChangeBounds();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ViewGroup sceneRoot = (ViewGroup) findViewById(R.id.scene_root);
        mScene1 = Scene.getSceneForLayout(sceneRoot, R.layout.scene1, this);
        mScene2 = Scene.getSceneForLayout(sceneRoot, R.layout.scen2, this);
    }

    public void onFabClick(View view) {
        if (!mIsBack) {
            mIsBack = true;
            TransitionManager.go(mScene2, mChangeBounds);
        } else {
            mIsBack = false;
            TransitionManager.go(mScene1, mChangeBounds);
        }
    }
}

代码很简单,我也不多做说明 ,接下来让我们来看看源码如何实现的。

源码实现

在分析之前,先来看2个贯穿整个源码的类,这2个类都是用来保存数据的,所以我们先要搞清楚它们是如何保存数据的。

第一个类,TransitionValues

public class TransitionValues {

    /**
     * The View with these values
     */
    public View view;

    /**
     * The set of values tracked by transitions for this scene
     */
    public final Map<String, Object> values = new ArrayMap<String, Object>();

    /**
     * The Transitions that targeted this view.
     */
    final ArrayList<Transition> targetedTransitions = new ArrayList<Transition>();

    // ...
}

TransitionValues 类比较简单,就是用这三个变量来保存数据

  • view:代表哪个 View 是需要做动画的
  • values:类型为 Map< String,Object>,其中 String 是 - Transition 的实现类定义的属性名,Object 是 Transition 的实现类定义的要保存的数据,这个我们在后面会看到。
  • targetedTransitions:类型为 ArrayList< Transition>,来保存 View 需要使用的各种 Transition。

第二个类,TransitionValuesMaps

class TransitionValuesMaps {
    ArrayMap<View, TransitionValues> viewValues =
            new ArrayMap<View, TransitionValues>();
    SparseArray<View> idValues = new SparseArray<View>();
    LongSparseArray<View> itemIdValues = new LongSparseArray<View>();
    ArrayMap<String, View> nameValues = new ArrayMap<String, View>();
}

这个类,说简单点,就相当于 4 个 map,那么这 4 个“map”作用是什么呢?

  • viewValues :保存 View 和 TransitionValues 对
  • idValues :保存 view.getId() 和 View 对
  • itemIdValues:这个是针对 ListView的,保存 ListView 的 Item id 和 View 对
  • nameValues :保存 View.getTransitionName() 和 View 对

创建Scene

Scene.getSceneForLayout()

    public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) {

        SparseArray<Scene> scenes = (SparseArray<Scene>) sceneRoot.getTag(
                R.id.transition_scene_layoutid_cache);
        if (scenes == null) {
            scenes = new SparseArray<Scene>();
            // 为 sceneRoot 创建一个 SparseArray(相当于map),保存 com.android.internal.R.id.scene_layoutid_cache 和 scenes 键值对
            sceneRoot.setTagInternal(com.android.internal.R.id.scene_layoutid_cache, scenes);
        }


        Scene scene = scenes.get(layoutId);
        if (scene != null) {
            return scene;
        } else {
            // 创建Scene对象的过程只是简单的赋值,分别赋值到 mSceneRoot, mContext, mLayoutId
            scene = new Scene(sceneRoot, layoutId, context);
            scenes.put(layoutId, scene);
            return scene;
        }
    }

第 8行,为 sceneRoot 创建一个 tag,这个 tag 是 SparseArray 类型(相当于 map),并保存了一对数据,key 为 R.id.transition_scene_layoutid_cache,value 为 mScenes(类型为 SparseArray< Scene>), 而这个 mScenes 保存了所有用 sceneRoot 创建的 Scene。

执行过渡动画

TransitionManager.go()

    private static Transition sDefaultTransition = new AutoTransition();

    public static void go(@NonNull Scene scene) {
        changeScene(scene, sDefaultTransition);
    }

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

        final ViewGroup sceneRoot = scene.getSceneRoot();
        if (!sPendingTransitions.contains(sceneRoot)) {
            // 把 sceneRoot 加入到 sPendingTranstions (类型为 ArrayList) 中
            sPendingTransitions.add(sceneRoot);

            // 做一次深拷贝
            Transition transitionClone = null;
            if (transition != null) {
                transitionClone = transition.clone();
                transitionClone.setSceneRoot(sceneRoot);
            }

            // 从sceneRoot的tag中获取 key 为 com.android.internal.R.id.current_scene 的值
            Scene oldScene = Scene.getCurrentScene(sceneRoot);
            if (oldScene != null && transitionClone != null &&
                    oldScene.isCreatedFromLayoutResource()) {
                transitionClone.setCanRemoveViews(true);
            }

            // step1: 保存 sceneRoot 及其 children 的信息到 mStartValues/mEndValues
            sceneChangeSetup(sceneRoot, transitionClone);

            // step2: 主要做的是,先移除 sceneRoot 中的所有 views,然后把新的布局加载进来
            scene.enter();

            // step3: 创建 Animator 并运行
            sceneChangeRunTransition(sceneRoot, transitionClone);
        }
    } 

代码中注释可以看出,调用 TransitionManager.go() 来完成 transition animation 之前,经历了三步,先看 step1,保存信息。

Step1->保存视图信息

TransitionManager 的 sceneChangeSetup()

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

        // 获取当前正在运行的 Transition
        ArrayList<Transition> runningTransitions = getRunningTransitions().get(sceneRoot);

        if (runningTransitions != null && runningTransitions.size() > 0) {
            for (Transition runningTransition : runningTransitions) {
                // 如果有正在运行的 Transition ,就停止,也就是让 Animator 暂停
                runningTransition.pause(sceneRoot);
            }
        }

        if (transition != null) {
            // 捕获sceneRoot下children的相关值,参数 true 表示这个捕获动作是发生在场景转换前
            transition.captureValues(sceneRoot, true);
        }

        // Notify previous scene that it is being exited
        // 获取sceneRoot相关的SparseArray中key为com.android.internal.R.id.current_scene的值
        Scene previousScene = Scene.getCurrentScene(sceneRoot);
        if (previousScene != null) {
            previousScene.exit();
        }
    }

现在只需要关注 15 行,这一行就把当前的 sceneRoot 数据保存到 transition 中。注意 transition.captureValues(sceneRoot, true) 的第二个参数,true 表示转换发生前,false 表示转换发生后,shit ! 这个方法会针对这个参数分别做出不同的行为,那么我只在这里分析一次,后面不再给出分析。

Transition 的 captureValues() 方法

    void captureValues(ViewGroup sceneRoot, boolean start) {
        // start 为 true/false,清空 mStartValues/mEndValues
        clearValues(start);
        // mTargetIds 是调用 Transition.addTargetIds()/removeTargetIds() 后才有值的
        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 {
            // 捕获sceneRoot,以及children的信息并保存到 mStartValues/mEndValues
            captureHierarchy(sceneRoot, start);
        }

        // mNameOverrides 是针对 Fragment shared elements transitions
        // TODO: 后面分析 
        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);
                }
            }
        }
    }  

第 3 行,清空 mStartValues/mEndValues,这两个变量都是 TransitionValuesMaps 类型(文章开头已经给出)。

由于我在例子中并没有为 Transition 设置 target,所以这段代码,关注47行,captureHierarchy()。这个方法是用来保存 sceneRoot 的信息,以及遍历 sceneRoot 的 children ,并且递归调用 captureHierarchy() 来保存所有 children 的信息。

Transition 的 captureHierarchy()

    private void captureHierarchy(View view, boolean start) {
        if (view == null) {
            return;
        }
        int id = view.getId();

        // mTargetIdExcludes 是在调用 Transition.excludeTarget(int targetId, boolean exclude) 后才有值
        if (mTargetIdExcludes != null && mTargetIdExcludes.contains(id)) {
            return;
        }
        // mTargetExcludes 是在调用 Transition.excludeTarget(View target, boolean exclude) 后才有值
        if (mTargetExcludes != null && mTargetExcludes.contains(view)) {
            return;
        }

        // mTargetTypeExcludes 是在调用 Transition.excludeTarget(Class type, boolean exclude) 后才有值
        if (mTargetTypeExcludes != null && view != null) {
            int numTypes = mTargetTypeExcludes.size();
            for (int i = 0; i < numTypes; ++i) {
                if (mTargetTypeExcludes.get(i).isInstance(view)) {
                    return;
                }
            }
        }

        if (view.getParent() instanceof ViewGroup) {
            TransitionValues values = new TransitionValues();
            values.view = view;
            if (start) {
                // 为 TransitionValues.values存值,保存起始场景View的属性值,自定义Transition类,这个方法要复写
                captureStartValues(values);
            } else {
                // 为 TransitionValues.values存值,保存结束场景View的属性值,自定义Transition类,这个方法要复写
                captureEndValues(values);
            }
            // 把当前 Transition 保存到 TransitionValues.targetedTransitions
            values.targetedTransitions.add(this);
            if (start) {
                // 为 mStartValues 的 4 个 map 存值
                addViewValues(mStartValues, view, values);
            } else {
                // 为 mEndValues 的 4 个 map 存值
                addViewValues(mEndValues, view, values);
            }
        }
        if (view instanceof ViewGroup) {
            // Don't traverse child hierarchy if there are any child-excludes on this view
            if (mTargetIdChildExcludes != null && mTargetIdChildExcludes.contains(id)) {
                return;
            }
            if (mTargetChildExcludes != null && mTargetChildExcludes.contains(view)) {
                return;
            }
            if (mTargetTypeChildExcludes != null) {
                int numTypes = mTargetTypeChildExcludes.size();
                for (int i = 0; i < numTypes; ++i) {
                    if (mTargetTypeChildExcludes.get(i).isInstance(view)) {
                        return;
                    }
                }
            }
            // 遍历,递归调用,直到根部局下所有的 child 都保存了值
            ViewGroup parent = (ViewGroup) view;
            for (int i = 0; i < parent.getChildCount(); ++i) {
                captureHierarchy(parent.getChildAt(i), start);
            }
        }
    } 

26 到 45 行,用来保存数据的。

63 到 66 行,是遍历 sceneRoot 的 children,递归调用 captureHierarchy() 方法来保存数据。 所以整个 captureHierarchy() 方法就中用来保存 View(包括 sceneRoot) 的数据。

那么保存数据关键的地方就是 29 到 35 行, captureStartValues()/captureEndValues() ,分别用来保存转换 前/后 布局的信息,这2个方法,是在 Transtion 类中是抽象方法,所以我们要用一个 Transition 实现类来分析,那么这里就用例子中的 ChangeBounds 类的 captureStartValues() 方法来分析。

ChangeBounds.java 的 captureStartValues()

    @Override
    public void captureStartValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }

    @Override
    public void captureEndValues(TransitionValues transitionValues) {
        captureValues(transitionValues);
    }

    private static final String PROPNAME_BOUNDS = "android:changeBounds:bounds";
    private static final String PROPNAME_CLIP = "android:changeBounds:clip";
    private static final String PROPNAME_PARENT = "android:changeBounds:parent";
    private static final String PROPNAME_WINDOW_X = "android:changeBounds:windowX";
    private static final String PROPNAME_WINDOW_Y = "android:changeBounds:windowY";

    private void captureValues(TransitionValues values) {
        View view = values.view;

        if (view.isLaidOut() || view.getWidth() != 0 || view.getHeight() != 0) {
            values.values.put(PROPNAME_BOUNDS, new Rect(view.getLeft(), view.getTop(),
                    view.getRight(), view.getBottom()));
            values.values.put(PROPNAME_PARENT, values.view.getParent());

            if (mReparent) {
                values.view.getLocationInWindow(tempLocation);
                values.values.put(PROPNAME_WINDOW_X, tempLocation[0]);
                values.values.put(PROPNAME_WINDOW_Y, tempLocation[1]);
            }

            if (mResizeClip) {
                values.values.put(PROPNAME_CLIP, view.getClipBounds());
            }
        }
    }

这段代码解释下,先看第 20 行,view.isLaidOut() 是说这个 view 已经至少被 layout() 了一次。其实这整行的意思就是 view 是否能获得宽高的值(不为0)。

第25行,mReparent 是由 setReparent() 设置的,mReparent 的作用是重新定义 View 的 parent ,不过这个功能已经在 changeTransform 类中实现,所以这里已经是 deprecated,默认为 false,而我们例子中也没有设置,所以就不探究了。

第31行,mResizeClip 是由 setResizeClip() 设置的,这个作用是,用 View 的 clip bounds 来实现动画,默认为 flase,同样,例子中并没有设置这个,所以也不用去探究。

那么整个代码,其实我们只需要关注的就是 21 到 23 行,从这里就可以看到 TransitionValues.values 保存的是什么数据。

那么,再回到 captureHierarchy() 方法的 37行,把当前的 transtion 保存到 values.targetedTranstions 中(如果不知道这是什么,看前面)。

再来看captureHierarchy() 方法的 38 到 44行 的 addViewValues() 方法,这个方法是把刚刚创建的局部变量 values( TransitionValues 对象 ) 的值保存到 mStartValues/mEndValues (TranstionValuesMaps 对象,前面提到过)中。

Transition 的 addViewValues() 方法

    private static void addViewValues(TransitionValuesMaps transitionValuesMaps,
            View view, TransitionValues transitionValues) {
        // step1
        transitionValuesMaps.mViewValues.put(view, transitionValues);
        int id = view.getId();
        if (id >= 0) {
            if (transitionValuesMaps.mIdValues.indexOfKey(id) >= 0) {
                // Duplicate IDs cannot match by ID.
                // 如果有重复的 id,那么就不去匹配了,直接把 id 和 null 对应
                transitionValuesMaps.mIdValues.put(id, null);
            } else {
                // step2
                transitionValuesMaps.mIdValues.put(id, view);
            }
        }
        String name = ViewCompat.getTransitionName(view);
        if (name != null) {
            if (transitionValuesMaps.mNameValues.containsKey(name)) {
                // Duplicate transitionNames: cannot match by transitionName.
                // 如果有重复的 transitionName,那么就不去匹配了,直接把 transitionName和 null 对应
                transitionValuesMaps.mNameValues.put(name, null);
            } else {
                // step3
                transitionValuesMaps.mNameValues.put(name, view);
            }
        }
        // 关于 ListView 的
        if (view.getParent() instanceof ListView) {
            ListView listview = (ListView) view.getParent();
            if (listview.getAdapter().hasStableIds()) {
                int position = listview.getPositionForView(view);
                long itemId = listview.getItemIdAtPosition(position);
                if (transitionValuesMaps.mItemIdValues.indexOfKey(itemId) >= 0) {
                    // Duplicate item IDs: cannot match by item ID.
                    View alreadyMatched = transitionValuesMaps.mItemIdValues.get(itemId);
                    if (alreadyMatched != null) {
                        ViewCompat.setHasTransientState(alreadyMatched, false);
                        transitionValuesMaps.mItemIdValues.put(itemId, null);
                    }
                } else {
                    ViewCompat.setHasTransientState(view, true);
                    // step4
                    transitionValuesMaps.mItemIdValues.put(itemId, view);
                }
            }
        }
    }

从注释中的 step1, step2, step3, step4 可知,分为 4 步保存数据,其中我们需要注意就是,同一个布局中不要出现重复的 id 或 重复的 transitionname,不然是不能实现动画的。 最后一个关于 ListView 的暂时还没研究是怎么回事。反正关注前三步就可以了。

估计有人看到这里有点懵逼了,会问自己我们到哪了?都干了点啥?那么现在总结下,到此为止,我们完成了 TransitionManager.go() 的 step1,我们已经将转换前的布局中的每个 view(包括 sceneRoot),保存信息了,保存到了 mStartValues (TransitionValuesMaps)中。

Step2->布局的切换

现在我们来分析 TransitionManager.go( scene ) 中的 step2,scene.enter(),这个方法主要的作用是先 remove sceneRoot 中 的children,然后再把 scene 的 layoutId 加载到 sceneRoot 中。

    public void enter() {
        // Apply layout change, if any
        // Scene 对象的创建方式有2中,一种传入 layoutId,一种传入 view,这里有满足
        if (mLayoutId > 0 || mLayout != null) {
            // empty out parent container before adding to it
            getSceneRoot().removeAllViews();

            // 加载进 mSceneRoot 中
            if (mLayoutId > 0) {
                LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
            } else {
                mSceneRoot.addView(mLayout);
            }
        }

        // Notify next scene that it is entering. Subclasses may override to configure scene.
        // mEnterAction 是一个 Runnable 对象,如果为 Scene 设置了 setEnterAction() 会调用
        if (mEnterAction != null) {
            mEnterAction.run();
        }

        // 为 mSceneRoot 的 tag(SparseArray对象) 的 key 为 R.id.transition_current_scene 设置相应的 value, 这个 value 就是当前的 end scene
        setCurrentScene(mSceneRoot, this);
    }

代码已经注释很清楚了,不多说了。

到此,已经完成了 TransitionManager.go(scene) 中的 step2,这一步比较简单。

Step3->生成并执行动画

再看 TransitionManager.go(scene) 的step3,sceneChangeRunTransition(),这一步是创建 Animator,并运行。

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

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

        @Override
        public boolean onPreDraw() {
            removeListeners();
            // TransitionManager.go() 的时候 add 过一次
            sPendingTransitions.remove(mSceneRoot);
            // 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) {
                // 如果不存在正在运行的 transition,就初始化
                currentTransitions = new ArrayList<>();
                runningTransitions.put(mSceneRoot, currentTransitions);
            } else if (currentTransitions.size() > 0) {
                // 如果有正在运行的 transtion, 就用 previousRunningTransitions 保存
                previousRunningTransitions = new ArrayList<>(currentTransitions);
            }
            currentTransitions.add(mTransition);
            mTransition.addListener(new Transition.TransitionListenerAdapter() {
                @Override
                public void onTransitionEnd(@NonNull Transition transition) {
                    ArrayList<Transition> currentTransitions = runningTransitions.get(mSceneRoot);
                    currentTransitions.remove(transition);
                }
            });
            // step1 保存 end scene 的信息
            mTransition.captureValues(mSceneRoot, false);
            if (previousRunningTransitions != null) {
                for (Transition runningTransition : previousRunningTransitions) {
                    // 继续运行之前暂停的 transition
                    runningTransition.resume(mSceneRoot);
                }
            }
            // step2 创建并运行动画
            mTransition.playTransition(mSceneRoot);

            return true;
        }
    }

在 step2 的时候,remove , add children 必然会导致视图刷新,那么现在关注 46 行,在 redraw 之前,调用 onPreDraw()。

在前面介绍过 sceneChangeSetup() 方法的时候,我们获取过一次运行的动画,如果有,就会暂停,代码在 sceneChangeSetup() 的第 6 到 11 行。那么这里的代码的 51 到 62,以及 72 到 78 行,就恢复之前暂停的动画。

那么现在把目光聚焦到 72 行,mTransition.captureValues(mSceneRoot, false); 这个方法我前面介绍过,总结下就是保存数据信息到 mEndValues(TransitionValuesMaps 对象),不熟悉的自己再过一遍。

那么现在把目光放到第 80 行,这里就是重点,创建动画,并执行动画。

    void playTransition(ViewGroup sceneRoot) {
        // mStartValuesList/mEndValuesList 这里才开始初始化
        mStartValuesList = new ArrayList<TransitionValues>();
        mEndValuesList = new ArrayList<TransitionValues>();

        // 过滤掉不匹配做动画的的View
        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();
    }

注意第3,4行的,这里才初始化了两个全局变量 mStartValuesList 和 mEndValuesList,类型为 ArrayList< TransitionValues >。

第7行,过滤掉不能匹配做动画的 View,看下matchStartAndEnd() 方法

    int[] mMatchOrder = DEFAULT_MATCH_ORDER;
    private static final int[] DEFAULT_MATCH_ORDER = {
        MATCH_NAME,
        MATCH_INSTANCE,
        MATCH_ID,
        MATCH_ITEM_ID,
    };
    private void matchStartAndEnd(TransitionValuesMaps startValues,
            TransitionValuesMaps endValues) {
        // 获取 mStartValues 和 mEndValues 的 viewValues 
        ArrayMap<View, TransitionValues> unmatchedStart =
                new ArrayMap<View, TransitionValues>(startValues.viewValues);
        ArrayMap<View, TransitionValues> unmatchedEnd =
                new ArrayMap<View, TransitionValues>(endValues.viewValues);

        // 按照 mMathOrder 的顺序找到匹配的项
        for (int i = 0; i < mMatchOrder.length; i++) {
            switch (mMatchOrder[i]) {
                case MATCH_INSTANCE:
                    // step2
                    matchInstances(unmatchedStart, unmatchedEnd);
                    break;
                case MATCH_NAME:
                    // step1
                    matchNames(unmatchedStart, unmatchedEnd,
                            startValues.nameValues, endValues.nameValues);
                    break;
                case MATCH_ID:
                    // step3
                    matchIds(unmatchedStart, unmatchedEnd,
                            startValues.idValues, endValues.idValues);
                    break;
                case MATCH_ITEM_ID:
                    // step4
                    matchItemIds(unmatchedStart, unmatchedEnd,
                            startValues.itemIdValues, endValues.itemIdValues);
                    break;
            }
        }
        // 
        addUnmatched(unmatchedStart, unmatchedEnd);
    }

第17行,按照 mMatchOrder 数组的顺序,开始过滤不匹配的 View,并保存匹配的 View,先看 step1 ,利用 transitionName 来过滤,也就是说设置了 transitionName 就可以有 transition animation。

    /**
     * Match start/end values by Adapter transitionName. Adds matched values to mStartValuesList
     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
     * startNames and endNames as a guide for which Views have unique transitionNames.
     */
    private void matchNames(ArrayMap<View, TransitionValues> unmatchedStart,
            ArrayMap<View, TransitionValues> unmatchedEnd,
            ArrayMap<String, View> startNames, ArrayMap<String, View> endNames) {
        int numStartNames = startNames.size();
        for (int i = 0; i < numStartNames; i++) {
            View startView = startNames.valueAt(i);
            if (startView != null && isValidTarget(startView)) {
                // 获取与 startNames 中有相同 transitionName 的 View
                View endView = endNames.get(startNames.keyAt(i));
                if (endView != null && isValidTarget(endView)) {
                    // 注意:进入这里的条件是 startView 和 endView 不为空,且 transitionName 相同
                    // 获取 transitionName 匹配成功的 startView 和 endView 的 TransitionValues
                    TransitionValues startValues = unmatchedStart.get(startView);
                    TransitionValues endValues = unmatchedEnd.get(endView);
                    // 如果找到 startValues 和 endValues 不为空
                    if (startValues != null && endValues != null) {
                        // 把得到的 startValues 和 endValues 放入到 mStartValuesList 和 mEndValuesList 中
                        mStartValuesList.add(startValues);
                        mEndValuesList.add(endValues);

                        // 把匹配成功的 View 从 unmatchedStart/unmatchedEnd 中移除
                        unmatchedStart.remove(startView);
                        unmatchedEnd.remove(endView);
                    }
                }
            }
        }
    }

首先我们要搞清楚,这个方法的四个参数到底是什么。

unmatchedStart 和 unmatchedEnd 是复制 mStartValues 和 mEndValues 的变量 viewValues 的值,注意是复制,而不是原本的值。

而 startNames 和 endValues 就是 mStartValues 和 mEndValues 的 nameValues 的值,这里就是原本的值了。

其实这个方法的注释我没有删除 ,因为它对这个方法描述的很清楚,如果还不明白就看下注释,所以这里就跳过了。

现在看看 matchStartAndEnd() 方法中的 step2

    /**
     * Match start/end values by View instance. Adds matched values to mStartValuesList
     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd.
     */
    private void matchInstances(ArrayMap<View, TransitionValues> unmatchedStart,
            ArrayMap<View, TransitionValues> unmatchedEnd) {
        for (int i = unmatchedStart.size() - 1; i >= 0; i--) {
            // 获取 unmatchedStart 的第 i 个 View
            View view = unmatchedStart.keyAt(i);
            if (view != null && isValidTarget(view)) {
                // 从 unmatchedEnd 找到相应 View 的 TransitionValues 值,并移除 View
                TransitionValues end = unmatchedEnd.remove(view);
                // 如果找到的 end (TransitionValues对象) 不为空,且 View 有效
                if (end != null && end.view != null && isValidTarget(end.view)) {
                    // 移除 View 并获取 TransitionValues 值
                    TransitionValues start = unmatchedStart.removeAt(i);
                    // 匹配成功,添加到 mStartValuesList 和 mEndValuesList 中
                    mStartValuesList.add(start);
                    mEndValuesList.add(end);
                }
            }
        }
    }

这个代码逻辑比较简单的,但是我这里确实有个疑问,前后两个 layout ,在怎样的情况下,用的是同一个 View? 这个我还没想出来,如果有人知道,还请赐教。

现在看看 matchStartAndEnd() 方法中的 step3

    /**
     * Match start/end values by Adapter view ID. Adds matched values to mStartValuesList
     * and mEndValuesList and removes them from unmatchedStart and unmatchedEnd, using
     * startIds and endIds as a guide for which Views have unique IDs.
     */
    private void matchIds(ArrayMap<View, TransitionValues> unmatchedStart,
            ArrayMap<View, TransitionValues> unmatchedEnd,
            SparseArray<View> startIds, SparseArray<View> endIds) {
        int numStartIds = startIds.size();
        for (int i = 0; i < numStartIds; i++) {
            View startView = startIds.valueAt(i);
            if (startView != null && isValidTarget(startView)) {
                View endView = endIds.get(startIds.keyAt(i));
                if (endView != null && isValidTarget(endView)) {
                    TransitionValues startValues = unmatchedStart.get(startView);
                    TransitionValues endValues = unmatchedEnd.get(endView);
                    if (startValues != null && endValues != null) {
                        mStartValuesList.add(startValues);
                        mEndValuesList.add(endValues);
                        unmatchedStart.remove(startView);
                        unmatchedEnd.remove(endView);
                    }
                }
            }
        }
    }

写注释我发现也是挺累的,所以我就这里直接来描述下,先是找到第 i 个 startView,如果 startView 不为 null,就找到与 startView 相同 id 的 endView,如果 endView 也不为空,那么就找出相应的 TransitionValues,并把它添加到 mStartValuesList 和 mEndValuesList 中,并且把匹配到 View 从 unmatchedStart 和 unmatchedEnd 移除。

而 matchStartAndEnd() 方法中的 step4 是针对 ListView 的,这里我就不分析了,我现在几乎没有用 ListView,如果还有用 ListView 的兄弟,可以根据我这篇文章继续深入分析。

那么现在回头整理下发型,哦不,是整理思路,我们现在已经分析完了 matchStartAndEnd() 的 step1 到 step3,那么现在 unmatchedStart 和 unmatchedEnd 中就剩下找不到妹子的 View 了, 找不到怎么办呢,new 一个呗,而 addUnmatched() 方法中 new 的这个妹子是 null。

    private void addUnmatched(ArrayMap<View, TransitionValues> unmatchedStart,
            ArrayMap<View, TransitionValues> unmatchedEnd) {
        // Views that only exist in the start Scene
        for (int i = 0; i < unmatchedStart.size(); i++) {
            final TransitionValues start = unmatchedStart.valueAt(i);
            if (isValidTarget(start.view)) {
                mStartValuesList.add(start);
                mEndValuesList.add(null);
            }
        }

        // Views that only exist in the end Scene
        for (int i = 0; i < unmatchedEnd.size(); i++) {
            final TransitionValues end = unmatchedEnd.valueAt(i);
            if (isValidTarget(end.view)) {
                mEndValuesList.add(end);
                mStartValuesList.add(null);
            }
        }
    }

我相信这段代码很好懂了吧,我就班门开斧了。

那么现在,我们再来总结下,根据 transitionName,id,View Instance,listView 的 item id 成功匹配到的 View , 把它们的 TransitionValues 加入到了 mStartValuesList 和 mEndValuesList 中了,而没有匹配成功的就用 null 来代替。

现在我们已经分析到了 playTransition() 方法的 46 行,这是就是创建动画,createAnimators()

    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);
            // 过滤异常情况
            if (start != null && !start.targetedTransitions.contains(this)) {
                start = null;
            }
            // 过滤异常情况
            if (end != null && !end.targetedTransitions.contains(this)) {
                end = null;
            }
            // 过滤同时为 null 的情况 
            if (start == null && end == null) {
                continue;
            }
            // Only bother trying to animate with values that differ between start/end
            // 如果 start 和 end 有差异,就代表需要改变
            boolean isChanged = start == null || end == null || isTransitionRequired(start, end);
            if (isChanged) {
                // 打印信息
                if (DBG) {
                    View view = (end != null) ? end.view : start.view;
                    Log.d(LOG_TAG, "  differing start/end values for view " + view);
                    if (start == null || end == null) {
                        Log.d(LOG_TAG, "    " + ((start == null) ?
                                "start null, end non-null" : "start non-null, end null"));
                    } else {
                        for (String key : start.values.keySet()) {
                            Object startValue = start.values.get(key);
                            Object endValue = end.values.get(key);
                            if (startValue != endValue && !startValue.equals(endValue)) {
                                Log.d(LOG_TAG, "    " + key + ": start(" + startValue +
                                        "), end(" + endValue + ")");
                            }
                        }
                    }
                }
                // TODO: what to do about targetIds and itemIds?
                // 为匹配到的 View 创建动画
                Animator animator = createAnimator(sceneRoot, start, end);
                if (animator != null) {
                    // Save animation info for future cancellation purposes
                    View view = null;
                    TransitionValues infoValues = null;
                    if (end != null) {
                        view = end.view;
                        String[] properties = getTransitionProperties();
                        if (view != null && properties != null && properties.length > 0) {
                            infoValues = new TransitionValues();
                            infoValues.view = view;
                            TransitionValues newValues = endValues.viewValues.get(view);
                            if (newValues != null) {
                                for (int j = 0; j < properties.length; ++j) {
                                    infoValues.values.put(properties[j],
                                            newValues.values.get(properties[j]));
                                }
                            }
                            int numExistingAnims = runningAnimators.size();
                            for (int j = 0; j < numExistingAnims; ++j) {
                                Animator anim = runningAnimators.keyAt(j);
                                AnimationInfo info = runningAnimators.get(anim);
                                if (info.values != null && info.view == view &&
                                        ((info.name == null && getName() == null) ||
                                                info.name.equals(getName()))) {
                                    if (info.values.equals(infoValues)) {
                                        // Favor the old animator
                                        animator = null;
                                        break;
                                    }
                                }
                            }
                        }
                    } else {
                        view = (start != null) ? start.view : null;
                    }
                    if (animator != null) {
                        // 如果调用过 setPropagation(),就获取每个 Transition 的动画延迟时间 
                        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);
            }
        }
    }

重点看51行,51行前都是一些条件过滤,createAnimator() 在 Transition 类中是一个抽象方法,所以需要子类去实现,我还是挑选 ChangeBounds 类来说明 ,由于代码比较多,只显示 ChangeBounds 用默认的方式 new ChangeBounds() 创建动画的代码。

    @Override
    public Animator createAnimator(final ViewGroup sceneRoot, TransitionValues startValues,
            TransitionValues endValues) {
        // 只有 startValues 和 endValues 都不为空才创建动画
        if (startValues == null || endValues == null) {
            return null;
        }

        // ...

        // 注意,这是获取的是 end scene 的 View
        final View view = endValues.view;
        // 如果不设置 mReparent 为 true,parentMatches() 默认返回为 true
        if (parentMatches(startParent, endParent)) {
            Rect startBounds = (Rect) startValues.values.get(PROPNAME_BOUNDS);
            Rect endBounds = (Rect) endValues.values.get(PROPNAME_BOUNDS);
            final int startLeft = startBounds.left;
            final int endLeft = endBounds.left;
            final int startTop = startBounds.top;
            final int endTop = endBounds.top;
            final int startRight = startBounds.right;
            final int endRight = endBounds.right;
            final int startBottom = startBounds.bottom;
            final int endBottom = endBounds.bottom;
            final int startWidth = startRight - startLeft;
            final int startHeight = startBottom - startTop;
            final int endWidth = endRight - endLeft;
            final int endHeight = endBottom - endTop;
            // 如果设置 mResizeClip = true ,并且为 View 调用过 setClipBounds(),这里才有值
            Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
            Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
            int numChanges = 0;
            if ((startWidth != 0 && startHeight != 0) || (endWidth != 0 && endHeight != 0)) {
                if (startLeft != endLeft || startTop != endTop) ++numChanges;
                if (startRight != endRight || startBottom != endBottom) ++numChanges;
            }
            if ((startClip != null && !startClip.equals(endClip)) ||
                    (startClip == null && endClip != null)) {
                ++numChanges;
            }
            if (numChanges > 0) {
                Animator anim;
                if (!mResizeClip) {
                    // 运行动画之前,先设置 view 的坐标为 start scene 的坐标
                    view.setLeftTopRightBottom(startLeft, startTop, startRight, startBottom);
                    // 如果不用 clip bounds 实现动画
                    if (numChanges == 2) {
                        // 如果宽高相等,只创建一个动画就可以达到整体平移的效果
                        if (startWidth == endWidth && startHeight == endHeight) {
                            Path topLeftPath = getPathMotion().getPath(startLeft, startTop, endLeft,
                                    endTop);
                            anim = ObjectAnimator.ofObject(view, POSITION_PROPERTY, null,
                                    topLeftPath);
                        } 
                        // 如果宽高有一个不相等,就需要创建两个动画来达到效果
                        else {
                            final ViewBounds viewBounds = new ViewBounds(view);
                            Path topLeftPath = getPathMotion().getPath(startLeft, startTop,
                                    endLeft, endTop);
                            ObjectAnimator topLeftAnimator = ObjectAnimator
                                    .ofObject(viewBounds, TOP_LEFT_PROPERTY, null, topLeftPath);

                            Path bottomRightPath = getPathMotion().getPath(startRight, startBottom,
                                    endRight, endBottom);
                            ObjectAnimator bottomRightAnimator = ObjectAnimator.ofObject(viewBounds,
                                    BOTTOM_RIGHT_PROPERTY, null, bottomRightPath);
                            AnimatorSet set = new AnimatorSet();
                            // 两个动画是同时进行的
                            set.playTogether(topLeftAnimator, bottomRightAnimator);
                            anim = set;
                            set.addListener(new AnimatorListenerAdapter() {
                                // We need a strong reference to viewBounds until the
                                // animator ends.
                                private ViewBounds mViewBounds = viewBounds;
                            });
                        }
                    } else if (startLeft != endLeft || startTop != endTop) {
                        // ...
                    } else {
                        // ...
                    }
                } else {
                    // ...
                }
                // 如果 Transition 运行期间,设置状态,不让 view 的 parent 刷新布局,Transition 结束后再恢复状态
                if (view.getParent() instanceof ViewGroup) {
                    final ViewGroup parent = (ViewGroup) view.getParent();
                    parent.suppressLayout(true);
                    TransitionListener transitionListener = new TransitionListenerAdapter() {
                        boolean mCanceled = false;

                        @Override
                        public void onTransitionCancel(Transition transition) {
                            parent.suppressLayout(false);
                            mCanceled = true;
                        }

                        @Override
                        public void onTransitionEnd(Transition transition) {
                            if (!mCanceled) {
                                parent.suppressLayout(false);
                            }
                        }

                        @Override
                        public void onTransitionPause(Transition transition) {
                            parent.suppressLayout(false);
                        }

                        @Override
                        public void onTransitionResume(Transition transition) {
                            parent.suppressLayout(true);
                        }
                    };
                    addListener(transitionListener);
                }
                return anim;
            }
        } else {
           // ...
        }
        return null;
    }

注释已经写的很清楚了,我这里就简单总结下,先是获取 end view,然后把这个 view 设置到转换前的的位置,然后再设置相应的动画来达到位移的效果。其实到这里,我们已经能把画面脑补出来了!

那么 createAnimators() 后面的代码就是保存创建的动画到 mAnimators,以及为动画设置延迟时间 ,大家自己看注释就行了,

那么现在,我们已经分析到了 playTransition() 的最后一行,runAnimators()。

    protected void runAnimators() {
        if (DBG) {
            Log.d(LOG_TAG, "runAnimators() on " + this);
        }
        // 调用 Transition 的 TransitionListener 的 onTransitionStart()
        start();
        ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
        // Now start every Animator that was previously created for this transition
        for (Animator anim : mAnimators) {
            if (DBG) {
                Log.d(LOG_TAG, "  anim: " + anim);
            }
            if (runningAnimators.containsKey(anim)) {
                start();
                // 运行动画
                runAnimator(anim, runningAnimators);
            }
        }
        // 运行完了就清除保存的动画
        mAnimators.clear();
        // 调用 Transition 的 TransitionListener 的 onTransitionEnd()
        end();
    }

    protected void start() {
        if (mNumInstances == 0) {
            if (mListeners != null && mListeners.size() > 0) {
                ArrayList<TransitionListener> tmpListeners =
                        (ArrayList<TransitionListener>) mListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onTransitionStart(this);
                }
            }
            mEnded = false;
        }
        mNumInstances++;
    }

    protected void end() {
        --mNumInstances;
        if (mNumInstances == 0) {
            if (mListeners != null && mListeners.size() > 0) {
                ArrayList<TransitionListener> tmpListeners =
                        (ArrayList<TransitionListener>) mListeners.clone();
                int numListeners = tmpListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    tmpListeners.get(i).onTransitionEnd(this);
                }
            }
            for (int i = 0; i < mStartValues.itemIdValues.size(); ++i) {
                View view = mStartValues.itemIdValues.valueAt(i);
                if (view != null) {
                    view.setHasTransientState(false);
                }
            }
            for (int i = 0; i < mEndValues.itemIdValues.size(); ++i) {
                View view = mEndValues.itemIdValues.valueAt(i);
                if (view != null) {
                    view.setHasTransientState(false);
                }
            }
            mEnded = true;
        }
    }

我们把目光放到第6行,这里就是运行刚刚创建的动画了。

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

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

啊,看到最后一行,动画就真的跑起来了,一切就到此为止了,我们框架分析也到此为止了。不过其他的细节,我就不细讲了,有兴趣的可以自己研究下代码的逻辑。

总结

本文通过一个例子做引子,从而探究了源码如何实现。然而本文只是抛砖引玉,让大家能了解 Transition Framework 是如何运作的,那么以后如果遇到什么问题,就可以追根溯源了。

Transition 的实现类有很多,例如 Slide , ChangeTransform, Explode 等等,具体这些效果是如何实现的,就留给大家自己分析了。

最后,如果我还想实现自己的动画,我们就需要自己实现 Transition 类,这将是一个非常有挑战的任务。

发布了50 篇原创文章 · 获赞 30 · 访问量 400万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览