Material Design - TransitionManager

材料設(shè)計其實在上一篇圖片和色彩之后,應(yīng)該是已經(jīng)結(jié)束了。但是后來又發(fā)現(xiàn)忘了動畫,這是讓材料設(shè)計煥發(fā)光彩的重點啊。所以又翻開了文檔和 google,開始了檢索和閱讀。

谷歌在 api 19 中添加了 android.transition 這個包,用于優(yōu)化安卓動畫的體驗。但是事實上,api 19 中 transition 中的類寥寥無幾,大部分的類都是在 api 21 之后新添加的。那么有人會說了,現(xiàn)在的工程最少也要兼容到 api 19 吧?這樣的類,并沒有什么使用價值。

話糙理不糙,我還是先放上 github 上有幾千 star 的兼容庫鏈接:這是鏈接。

這位前輩是真厲害,據(jù)說谷歌工程師解決部分 transition 包中 bug 的解決方案都是直接從這里來的。雖然有現(xiàn)成的庫可以使用,但是原來的東西還是需要會用。所以來一起看看吧。

今天,主要看一下這里最基礎(chǔ)的 TransitionManager


google 文檔開頭就提出了三個類,TransitionManager 、Transition、 Scene。
根據(jù)英文名稱不難看出各個類的基本作用:

  • TransitionManager:動畫的管理類,其中封裝了 Transition 和 Scene
  • Scene:場景,它記錄了 ViewTree 的某個時刻的關(guān)鍵幀,它通常作為動畫的起始幀和最終幀使用
  • Transition:過渡,它代表了這個動畫的過渡方式,包括漸變透明(Fade)、滑動(Slide) 等等

介紹就到這里,如果需要更多參考信息,可以移步 google 文檔。


一、TransitionManager API

去除參數(shù),只看方法名一共有以下幾種:

方法名 作用 備注
beginDelayedTransition 以當前幀為起始幀,直到下一次繪制后為結(jié)束幀,補齊中間的過渡動畫 Convenience method to animate to a new scene defined by all changes within the given scene root between calling this method and the next rendering frame.
endTransitions 結(jié)束所有過渡動畫 Ends all pending and ongoing transitions on the specified scene root.
go 以當前幀為起始幀,傳入?yún)?shù)為結(jié)束幀,補齊中間的過渡動畫 Convenience method to simply change to the given scene using the given transition.
setTransition 根據(jù)傳入?yún)?shù)確認起始幀和結(jié)束幀,補齊中間的過渡動畫 Sets a specific transition to occur when the given pair of scenes is exited/entered.
transitionTo 以當前幀為起始幀,傳入?yún)?shù)為結(jié)束幀,補齊中間的過渡動畫 using the appropriate transition for this particular scene change (as specified to the TransitionManager, or the default if no such transition exists)

beginDelayedTransition 是一種特別方便,又好用的方法。

查看源碼可以看到這個方法將會發(fā)生變化的 ViewGroup 緩存起來,并給它添加了 再次繪制的監(jiān)聽器:

public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition) {
    if (!sPendingTransitions.contains(sceneRoot) && sceneRoot.isLaidOut()) {
        //debug 模式日志
        if (Transition.DBG) {
            Log.d(LOG_TAG, "beginDelayedTransition: root, transition = " +
                    sceneRoot + ", " + transition);
        }
        //rootView 緩存
        sPendingTransitions.add(sceneRoot);
        if (transition == null) {
            transition = sDefaultTransition;
        }
        //過渡方式
        final Transition transitionClone = transition.clone();
        //設(shè)置切換到當前場景的過渡
        sceneChangeSetup(sceneRoot, transitionClone);
        //設(shè)置 rootView 為當前場景
        Scene.setCurrentScene(sceneRoot, null);
        //添加繪制監(jiān)聽,在合適時機確認最終幀,并實現(xiàn)過渡
        sceneChangeRunTransition(sceneRoot, transitionClone);
    }
}

private static void sceneChangeRunTransition(final ViewGroup sceneRoot,
        final Transition transition) {
    if (transition != null && sceneRoot != null) {
        //封裝了 OnAttachStateChangeListener 和 OnPreDrawListener
        MultiListener listener = new MultiListener(transition, sceneRoot);
        sceneRoot.addOnAttachStateChangeListener(listener);
        sceneRoot.getViewTreeObserver().addOnPreDrawListener(listener);
    }
}

beginDelayedTransition 可以在代碼中直接應(yīng)用過渡, View 變換大小后,它會在下一次繪制的時候執(zhí)行過渡,從而使得這個過程不那么突兀。

TransitionManager.beginDelayedTransition(mRootView);
ViewGroup.LayoutParams layoutParams = mSquareView.getLayoutParams();
layoutParams.height = newSize;
layoutParams.width = newSize;
mSquareView.setLayoutParams(layoutParams);
GIF.gif

上面的例子是直接使用 LinearLayout 設(shè)置背景色獲取的方塊。當然普通 view 本身的大小變化也可以獲得過渡效果,但是如果你使用的是自定義 View 可能你獲得的過渡效果和想象的會有所不同。

這里我們用自定義 View 圓點視圖為例,為了讓效果更明顯,我給這個 PointView 的容器添加了背景,獲取效果為:

GIF.gif

產(chǎn)生這樣的效果,是因為默認的 AutoTransition 是 Fade 和 ChangeBound 的組合,其中大小變化由 ChangeBound 完成,它能達到的效果只對 ViewGroup 生效。

TransitionManager 后面的方法都與 Scene 相關(guān),看得出 Scene 也是個很重要的類。因此,接下來讓我們了解一下 Scene 的用法。

三、Scene 場景

文檔閱讀 : 鏈接

我們通常把 Scene 譯為場景,這個翻譯其實還是挺好的。一個過渡動畫其實就是從一個場景到另一個場景的過渡,這里的兩個場景我們?nèi)∶麨?起始幀結(jié)束幀。而過渡動畫,就是將場景中所有的子視圖從起始幀移動到結(jié)束幀的運動效果。

1)創(chuàng)建 Scene 對象
根據(jù)文檔,我們可以看到,一共有兩種方式可以獲取到我們需要的 Scene 對象。

//1.構(gòu)造器
Scene(ViewGroup rootView)// 沒有過渡信息,使用時需要自行添加過渡動畫處理
Scene(ViewGroup rootView, View layout)//當這個場景進入時,會移除 rootView 中所有的子視圖
//2.靜態(tài)方法
getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)

其中對于構(gòu)造器創(chuàng)建的 Scene 對象,需要注意的事項都已經(jīng)備注在上面了,一般情況下,我們還是會使用靜態(tài)方法獲取。

2)一個例子
我們先來看一個例子:


起始幀
結(jié)束幀

這是我寫的兩個布局文件 scene1.xml 和 scene2.xml ,這個布局比較簡單,所以也就不貼了,需要注意的是,這里只有一層 ViewGroup ,所有 ImageView 都在同一個層級下,且視圖的 id 需要一一對應(yīng)。

按鈕代碼:

mRootView = ((ViewGroup) findViewById(R.id.activity_scene_rootview));
((RadioGroup) findViewById(R.id.activity_scene_radiogroup)).setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
    @Override
    public void onCheckedChanged(RadioGroup group, @IdRes int checkedId) {
        switch (checkedId) {
            case R.id.activity_scene_radiobtn1:
                Scene scene1 = Scene.getSceneForLayout(mRootView, R.layout.scene1, SceneActivity.this);
                TransitionManager.go(scene1);
                break;
            case R.id.activity_scene_radiobtn2:
                Scene scene2 = Scene.getSceneForLayout(mRootView, R.layout.scene2, SceneActivity.this);
                TransitionManager.go(scene2);
                break;
        }
    }
});

來看一下效果:


效果圖

3)其他方法
在文檔中,Scene 有 enter() 、exit() 、setEnterAction() 、 setExitAction()
enter 和 exit 方法就不多說了,在 TransitionManager 中,切換 Scene 的方法中也是調(diào)用了它們。關(guān)于后面兩個方法,我沒有找到合適使用他們的場景,但是文檔說明中指出,這兩個方法用于沒有使用布局資源或?qū)哟谓Y(jié)構(gòu)定義的場景,或者在這些層次結(jié)構(gòu)更改后需要執(zhí)行附加步驟的場景。
有點抽象,之后如果有遇到合適的場景,再看吧。

四、Transition
效果圖-g.png

上面是 google 文檔的截圖,可以看到 Transition 的子類非常豐富。實現(xiàn)不同接口的子類,組合出了多種多樣的過渡效果。

1)介紹
Transition 類承載了切換到目標場景所有的動畫效果,它的子類可以實現(xiàn)一組動畫,也可以自定義實現(xiàn)動畫。Transition 有兩個核心任務(wù):1.記錄特定的屬性;2.根據(jù)記錄屬性的變化執(zhí)行過渡動畫。

2)聲明一個 TransitionSet 對象
TransitionSet 對象可以在 xml 文件中初始化,路徑為:res/transition
例如我們創(chuàng)建一個帶有 explode、changeBounds、changeTransform、changeClipBounds、changeImageTransform 的過渡動畫。

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <explode/>
    <changeBounds/>
    <changeTransform/>
    <changeClipBounds/>
    <changeImageTransform/>
</transitionSet>

相應(yīng)的,各個 Transition 有多種屬性,這里就不再貼出,需要的時候可以瀏覽文檔。

3)利用 TransitionManager 將動畫應(yīng)用到指定的 View 上

4)如果 explode、changeBounds 等自帶的 Transition 并沒有完成你需要的效果,那么你也可以用 transition 標簽來聲明:

<transition class="com.arno.CustomTransition"/>

CustomTransition 是一個自定義的動畫。自定義動畫和自定義 View 很像,它需要繼承 Transition 類,并實現(xiàn)三個方法:

// 記錄動畫起始幀
public void captureStartValues(TransitionValues transitionValues)
// 記錄動畫終結(jié)幀
public void captureEndValues(TransitionValues transitionValues)
// 根據(jù)記錄的起始幀和終結(jié)幀的屬性,創(chuàng)建動畫
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, final TransitionValues endValues)

這里,我們簡單實現(xiàn)一個改變布局高度的動畫。
首先在captureStartValues方法中獲取動畫起始高度屬性:

@Override
public void captureStartValues(TransitionValues transitionValues) {
    if (transitionValues == null) {
        return;
    }
    transitionValues.values.put(VIEW_HEIGHT,transitionValues.view.getHeight());
}

然后在captureEndValues中獲取動畫結(jié)束高度屬性:

@Override
public void captureEndValues(TransitionValues transitionValues) {
    if (transitionValues == null) {
        return;
    }
    transitionValues.values.put(VIEW_HEIGHT,transitionValues.view.getHeight());
}

最后在createAnimator中創(chuàng)建動畫:

@Override
public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues) {
    if (startValues == null || endValues == null) { return null;}

    final View endView = endValues.view;

    final int startHeight = (int) startValues.values.get(VIEW_HEIGHT);
    final int endHeight = (int) endValues.values.get(VIEW_HEIGHT);

    ValueAnimator sizeAnimator = ValueAnimator.ofInt(startHeight, endHeight);
    sizeAnimator.setDuration(500);
    sizeAnimator.setInterpolator(new LinearInterpolator());

    sizeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator valueAnimator) {
            int current = (int) valueAnimator.getAnimatedValue();
            endView.getLayoutParams().height = current;
            endView.requestLayout();
        }
    });

    AnimatorSet set = new AnimatorSet();
    set.play(sizeAnimator);

    return set;
}
GIF.gif

之后會再看看把 Share Element 和 Transition 結(jié)合,做頁面跳轉(zhuǎn)動畫。

以上。

感謝:
Material-Animations

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

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,725評論 25 709
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺ios動畫全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,686評論 6 30
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,258評論 5 13
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,033評論 4 61
  • 從創(chuàng)文明城市以來,大隊領(lǐng)導(dǎo)身先士卒,包崗到人,每天早晚高峰親自上崗和對各類非機動車違章進行査糾。 為了更...
    董班長閱讀 718評論 2 1

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