Android Transition 概念介紹及源碼解析

之前在github上尋找Activity轉(zhuǎn)場(chǎng)時(shí)偶然看到一個(gè)令人驚艷的項(xiàng)目:
Material-Animations(另有網(wǎng)友貢獻(xiàn)了中文版本
其中的效果有很多,我們著重看看其中比較常見(jiàn)的效果。
如:同一個(gè)Layout內(nèi)視圖變化時(shí)的動(dòng)畫(huà)

scenes_anim.gif

之所以說(shuō)驚艷,主要是因?yàn)橄襁@么復(fù)雜的動(dòng)畫(huà)效果,居然通篇不見(jiàn)Animation,只用了幾行代碼就搞定,頗有一種四兩撥千斤的感覺(jué)。

其實(shí)現(xiàn)方式就是今天的主角:Transition 。本文重說(shuō)概念,先簡(jiǎn)單介紹Transition的使用,再嘗試從源碼角度去分析,一探究竟。

Android Master Transition 源代碼目錄- master分支
Android Master Transition 源代碼目錄- 4.3KitKat分支

demo的效果如果自己使用Animation實(shí)現(xiàn)的話,代碼會(huì)極其冗雜,需要計(jì)算四個(gè)View在切換時(shí)的最終坐標(biāo),推算出其動(dòng)畫(huà)過(guò)程。(更不要說(shuō)圖中的動(dòng)畫(huà)還有旋轉(zhuǎn)等。。)

而這些在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即為T(mén)ransition一個(gè)子類(lèi)*/
            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    }
}

非常簡(jiǎn)短,看上去簡(jiǎn)單設(shè)置了幾個(gè)變換前后的layout就完成了任務(wù)。

事實(shí)上,Transition Framework提供的api遠(yuǎn)不止例子中的這幾個(gè),還有多種變形。但是只要我們厘清其核心的幾個(gè)類(lèi),必能以不變應(yīng)萬(wàn)變。
在這里首先向大家拿出三個(gè)核心概念。

概念

[1].scene root
其實(shí)就是一個(gè)ViewGroup,transition將會(huì)作用的主要布局,栗子中是一個(gè)簡(jiǎn)單的FrameLayout。
<!--sceneRoot -->
 <FrameLayout
            android:id="@+id/scene_root"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
[2].scenes
記錄了scene root 的某一個(gè)狀態(tài)  ,可以使用scene root 和某一個(gè)具體的layoutId來(lái)定義。

可以看做是ViewGroup的一個(gè)wrapper(封裝)

<!--用來(lái)定義scene1的布局文件,scene2、3、4類(lèi)似,只是各個(gè)ImageView的相對(duì)位置發(fā)生了變化-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical">


  <ImageView
      android:id="@+id/square_green"
      style="@style/MaterialAnimations.Icon.Big"
      android:src="@drawable/circle_24dp"
      android:tint="@color/sample_green" />

  <ImageView
      android:id="@+id/square_red"
      style="@style/MaterialAnimations.Icon.Big"
      android:layout_alignParentRight="true"
      android:src="@drawable/circle_24dp"
      android:tint="@color/sample_red" />

  <ImageView
      android:id="@+id/square_blue"
      style="@style/MaterialAnimations.Icon.Big"
      android:layout_below="@+id/square_green"
      android:src="@drawable/circle_24dp"
      android:tint="@color/sample_blue" />

  <ImageView
      android:id="@+id/square_yellow"
      style="@style/MaterialAnimations.Icon.Big"
      android:layout_alignParentRight="true"
      android:layout_below="@+id/square_red"
      android:src="@drawable/circle_24dp"
      android:tint="@color/sample_yellow" />
</RelativeLayout>
[3].Transition
最核心的部分,它會(huì)比對(duì)布局調(diào)整前后的不同,并且創(chuàng)建出合適的Animation或者Animation組合

可以先看一眼這三者的關(guān)系:

transitions_diagram.png

Scene 使用layout來(lái)定義,而TransitionMananger持有了starting scene 、ending scene以及Transition,并且作為入口(TransitionManange #go),托管了變換的過(guò)程。

過(guò)程

整個(gè)變換過(guò)程總結(jié)起來(lái)如下:

1. 記錄 Scene中Scene Root 的初始狀態(tài)
2. 應(yīng)用Ending Layout
3. 記錄Scene中Scene Root在第二步之后的狀態(tài)
4. 根據(jù)1、3步獲取的結(jié)果,設(shè)置Animation ,并且運(yùn)行之

這個(gè)過(guò)程基本上涵括在了

//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 的狀態(tài)**/
           sceneChangeSetup(sceneRoot, transitionClone);
           /**2. 應(yīng)用Ending Layout**/
           scene.enter();
           /** 3. 記錄Scene中Scene Root在第二步之后的狀態(tài)
           4. 根據(jù)1、3步獲取的結(jié)果,設(shè)置Animation ,并且運(yùn)行 **/
           sceneChangeRunTransition(sceneRoot, transitionClone);
       }
   }

1 .記錄Starting Scene 的狀態(tài)

    //TransitionMananger.java
    /**

     * @param sceneRoot  從StartingScene中獲取,ViewGroup
     * @param transition 設(shè)置的Transition
     */
  private static void sceneChangeSetup(ViewGroup sceneRoot, Transition transition) {

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

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

        if (transition != null) {
   //Transition 負(fù)責(zé)捕獲、記錄sceneRoot的起始狀態(tài)
            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<Transition> 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. 應(yīng)用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);
    }

最好理解的一部分,就是去應(yīng)用EndingScene。

3. 記錄Scene中Scene Root在第二步之后的狀態(tài)
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的屬性變化自然不會(huì)立即變化,而是要等下一次View的measure和layout完成之后。因此第三步的真正實(shí)現(xiàn)還是在view的onPreDraw回調(diào)中實(shí)現(xiàn)的。

public boolean onPreDraw() {
    removeListeners();

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

   (...)
  /** 記錄已經(jīng)measured和layout的SceneRoot屬性**/
    mTransition.captureValues(mSceneRoot, false);
    if (previousRunningTransitions != null) {
        for (Transition runningTransition : previousRunningTransitions) {
            runningTransition.resume(mSceneRoot);
        }
    }
  /**第四步的實(shí)現(xiàn)**/
    mTransition.playTransition(mSceneRoot);

    return true;
}
4. 根據(jù)1、3步獲取的結(jié)果,設(shè)置Animation ,并且運(yùn)行之

1、3 步分別捕獲了Scene Root的前后狀態(tài),接下來(lái)根據(jù)這里計(jì)算出Animation。
也是自定義Transition最關(guān)鍵的部分,將在后面的系列中為大家解讀。

對(duì)于另外一個(gè)api,Transition#beginDelayedTransition其內(nèi)容幾乎和上文中的完全相同,區(qū)別在于:上面中第2步其實(shí)是由我們自己來(lái)實(shí)現(xiàn)。

看下這個(gè)例子:


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的實(shí)現(xiàn):

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

總結(jié)

這里,我們大體對(duì)Transition的實(shí)現(xiàn)有了一個(gè)大體概念,也知曉了其大致的實(shí)現(xiàn)思路。其實(shí)TransitionManager 的實(shí)現(xiàn)比較簡(jiǎn)單,其結(jié)構(gòu)也很清晰。因此,對(duì)于想探究Android源碼的同學(xué)來(lái)說(shuō),是一個(gè)不可多得的例子。

Transition中還有另外幾個(gè)有意思的部分: 如Transition生成動(dòng)畫(huà)的具體過(guò)程,Activity 和Fragment切換時(shí)應(yīng)用Transition等就是我們后面要研究的重點(diǎn)了。

本文的另一個(gè)啟示是:Transition并不是獨(dú)立于Animation Framework之外的一個(gè)獨(dú)立的體系,它可以看做是一個(gè)對(duì)于Animation的封裝。對(duì)于廣大的Android APP開(kāi)發(fā)工程師而言,可能我們平日里接觸到都是FrameWork層,但是即使是在FrameWork層中,也是有很多層級(jí)關(guān)系的。這對(duì)于我們“應(yīng)該”做什么,“能做”什么有很好的指導(dǎo) 作用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容