Android Transition 概念介绍及源码解析

之前在github上寻找Activity转场时偶然看到一个令人惊艳的项目:
Material-Animations(另有网友贡献了中文版本)
其中的效果有很多,我们着重看看其中比较常见的效果。
如:同一个Layout内视图变化时的动画

Android Transition 概念介绍及源码解析_第1张图片
scenes_anim.gif

之所以说惊艳,主要是因为像这么复杂的动画效果,居然通篇不见Animation,只用了几行代码就搞定,颇有一种四两拨千斤的感觉。

其实现方式就是今天的主角:Transition 。本文重说概念,先简单介绍Transition的使用,再尝试从源码角度去分析,一探究竟。

Android Master Transition 源代码目录- master分支
Android Master Transition 源代码目录- 4.3KitKat分支

demo的效果如果自己使用Animation实现的话,代码会极其冗杂,需要计算四个View在切换时的最终坐标,推算出其动画过程。(更不要说图中的动画还有旋转等。。)

而这些在Transition的面前,需要我们做的事情就少得多了:

ViewGroup sceneRoot = (ViewGroup) findViewById(R.id.scene_root);

(...)

scene1 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene1, this);
scene2 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene2, this);
scene3 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene3, this);
scene4 = Scene.getSceneForLayout(sceneRoot, R.layout.activity_animations_scene4, this);

(...)

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.button1:
            TransitionManager.go(scene1, new ChangeBounds());/*ChangeBound即为Transition一个子类*/
            break;
        case R.id.button2:
            TransitionManager.go(scene2, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds));
            break;
        case R.id.button3:
            TransitionManager.go(scene3, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential));
            break;
        case R.id.button4:
            TransitionManager.go(scene4, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));
            break;  
1n    }
}

非常简短,看上去简单设置了几个变换前后的layout就完成了任务。

事实上,Transition Framework提供的api远不止例子中的这几个,还有多种变形。但是只要我们厘清其核心的几个类,必能以不变应万变。
在这里首先向大家拿出三个核心概念。

概念

[1].scene root
其实就是一个ViewGroup,transition将会作用的主要布局,栗子中是一个简单的FrameLayout。

 
[2].scenes
记录了scene root 的某一个状态  ,可以使用scene root 和某一个具体的layoutId来定义。

可以看做是ViewGroup的一个wrapper(封装)





  

  

  

  

[3].Transition
最核心的部分,它会比对布局调整前后的不同,并且创建出合适的Animation或者Animation组合

可以先看一眼这三者的关系:

Android Transition 概念介绍及源码解析_第2张图片
transitions_diagram.png

Scene 使用layout来定义,而TransitionMananger持有了starting scene 、ending scene以及Transition,并且作为入口(TransitionManange #go),托管了变换的过程。

过程

整个变换过程总结起来如下:

1. 记录 Scene中Scene Root 的初始状态
2. 应用Ending Layout
3. 记录Scene中Scene Root在第二步之后的状态
4. 根据1、3步获取的结果,设置Animation ,并且运行之

这个过程基本上涵括在了

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

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

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

           Scene oldScene = Scene.getCurrentScene(sceneRoot);
           if (oldScene != null && transitionClone != null &&
                   oldScene.isCreatedFromLayoutResource()) {
               transitionClone.setCanRemoveViews(true);
           }
           /**1. 记录Starting Scene 的状态**/
           sceneChangeSetup(sceneRoot, transitionClone);
           /**2. 应用Ending Layout**/
           scene.enter();
           /** 3. 记录Scene中Scene Root在第二步之后的状态
           4. 根据1、3步获取的结果,设置Animation ,并且运行 **/
           sceneChangeRunTransition(sceneRoot, transitionClone);
       }
   }

1 .记录Starting Scene 的状态

    //TransitionMananger.java
    /**

     * @param sceneRoot  从StartingScene中获取,ViewGroup
     * @param transition 设置的Transition
     */
  private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {

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

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

        if (transition != null) {
   //Transition 负责捕获、记录sceneRoot的起始状态
            transition.captureValues(sceneRoot, true);
        }

        // Notify previous scene that it is being exited
        Scene previousScene = Scene.getCurrentScene(sceneRoot);
        if (previousScene != null) {
            previousScene.exit();
        }
    }   // Capture current values
        ArrayList runningTransitions = getRunningTransitions().get(sceneRoot);

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

        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();
        }
2. 应用Ending Layout
 //Scene.java
 public void enter() {

        // Apply layout change, if any
        if (mLayoutId > 0 || mLayout != null) {
            // empty out parent container before adding to it
            getSceneRoot().removeAllViews();

            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.
        if (mEnterAction != null) {
            mEnterAction.run();
        }

        setCurrentScene(mSceneRoot, this);
    }

最好理解的一部分,就是去应用EndingScene。

3. 记录Scene中Scene Root在第二步之后的状态
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);
        }
    }

view的属性变化自然不会立即变化,而是要等下一次View的measure和layout完成之后。因此第三步的真正实现还是在view的onPreDraw回调中实现的。

public boolean onPreDraw() {
    removeListeners();

    // Don't start the transition if it's no longer pending.
    if (!sPendingTransitions.remove(mSceneRoot)) {
        return true;
    }

   (...)
  /** 记录已经measured和layout的SceneRoot属性**/
    mTransition.captureValues(mSceneRoot, false);
    if (previousRunningTransitions != null) {
        for (Transition runningTransition : previousRunningTransitions) {
            runningTransition.resume(mSceneRoot);
        }
    }
  /**第四步的实现**/
    mTransition.playTransition(mSceneRoot);

    return true;
}
4. 根据1、3步获取的结果,设置Animation ,并且运行之

1、3 步分别捕获了Scene Root的前后状态,接下来根据这里计算出Animation。
也是自定义Transition最关键的部分,将在后面的系列中为大家解读。

对于另外一个api,Transition#beginDelayedTransition其内容几乎和上文中的完全相同,区别在于:上面中第2步其实是由我们自己来实现。

看下这个例子:


Android Transition 概念介绍及源码解析_第3张图片
view_layout_anim.gif

而其源码:
TransitionManager#beginDelayedTransition

 public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
    if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
        if (Transition.DBG) {
            Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
                    sceneRoot + ", " + transition);
        }
        sPendingTransitions.add(sceneRoot);
        if (transition == null) {
            transition = sDefaultTransition;
        }
        final Transition transitionClone = transition.clone();
        sceneChangeSetup(sceneRoot, transitionClone);
    
        Scene.setCurrentScene(sceneRoot, null);  //这里不同
        sceneChangeRunTransition(sceneRoot, transitionClone);
    }
}

demo的实现:

TransitionManager.beginDelayedTransition(sceneRoot);
ViewGroup.LayoutParams params = greenIconView.getLayoutParams();
params.width = 200;
greenIconView.setLayoutParams(params);

总结

这里,我们大体对Transition的实现有了一个大体概念,也知晓了其大致的实现思路。其实TransitionManager 的实现比较简单,其结构也很清晰。因此,对于想探究Android源码的同学来说,是一个不可多得的例子。

Transition中还有另外几个有意思的部分: 如Transition生成动画的具体过程,Activity 和Fragment切换时应用Transition等就是我们后面要研究的重点了。

本文的另一个启示是:Transition并不是独立于Animation Framework之外的一个独立的体系,它可以看做是一个对于Animation的封装。对于广大的Android APP开发工程师而言,可能我们平日里接触到都是FrameWork层,但是即使是在FrameWork层中,也是有很多层级关系的。这对于我们“应该”做什么,“能做”什么有很好的指导 作用。

你可能感兴趣的:(Android Transition 概念介绍及源码解析)