Material Design - TransitionManager

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

谷歌在 api 19 中添加了 android.transition 這個(gè)包,用于優(yōu)化安卓動(dòng)畫的體驗(yàn)隔缀。但是事實(shí)上题造,api 19 中 transition 中的類寥寥無幾,大部分的類都是在 api 21 之后新添加的猾瘸。那么有人會(huì)說了界赔,現(xiàn)在的工程最少也要兼容到 api 19 吧?這樣的類牵触,并沒有什么使用價(jià)值淮悼。

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

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

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


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

  • TransitionManager:動(dòng)畫的管理類堰汉,其中封裝了 Transition 和 Scene
  • Scene:場(chǎng)景,它記錄了 ViewTree 的某個(gè)時(shí)刻的關(guān)鍵幀伟墙,它通常作為動(dòng)畫的起始幀和最終幀使用
  • Transition:過渡翘鸭,它代表了這個(gè)動(dòng)畫的過渡方式,包括漸變透明(Fade)戳葵、滑動(dòng)(Slide) 等等

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


一档址、TransitionManager API

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

方法名 作用 備注
beginDelayedTransition 以當(dāng)前幀為起始幀邻梆,直到下一次繪制后為結(jié)束幀守伸,補(bǔ)齊中間的過渡動(dòng)畫 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é)束所有過渡動(dòng)畫 Ends all pending and ongoing transitions on the specified scene root.
go 以當(dāng)前幀為起始幀,傳入?yún)?shù)為結(jié)束幀浦妄,補(bǔ)齊中間的過渡動(dòng)畫 Convenience method to simply change to the given scene using the given transition.
setTransition 根據(jù)傳入?yún)?shù)確認(rèn)起始幀和結(jié)束幀尼摹,補(bǔ)齊中間的過渡動(dòng)畫 Sets a specific transition to occur when the given pair of scenes is exited/entered.
transitionTo 以當(dāng)前幀為起始幀,傳入?yún)?shù)為結(jié)束幀剂娄,補(bǔ)齊中間的過渡動(dòng)畫 using the appropriate transition for this particular scene change (as specified to the TransitionManager, or the default if no such transition exists)

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

查看源碼可以看到這個(gè)方法將會(huì)發(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è)置切換到當(dāng)前場(chǎng)景的過渡
        sceneChangeSetup(sceneRoot, transitionClone);
        //設(shè)置 rootView 為當(dāng)前場(chǎng)景
        Scene.setCurrentScene(sceneRoot, null);
        //添加繪制監(jiān)聽和二,在合適時(shí)機(jī)確認(rèn)最終幀,并實(shí)現(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 變換大小后惯吕,它會(huì)在下一次繪制的時(shí)候執(zhí)行過渡,從而使得這個(gè)過程不那么突兀怕午。

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

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

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

GIF.gif

產(chǎn)生這樣的效果羽戒,是因?yàn)槟J(rèn)的 AutoTransition 是 Fade 和 ChangeBound 的組合缤沦,其中大小變化由 ChangeBound 完成,它能達(dá)到的效果只對(duì) ViewGroup 生效易稠。

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

三养晋、Scene 場(chǎng)景

文檔閱讀 : 鏈接

我們通常把 Scene 譯為場(chǎng)景衬吆,這個(gè)翻譯其實(shí)還是挺好的。一個(gè)過渡動(dòng)畫其實(shí)就是從一個(gè)場(chǎng)景到另一個(gè)場(chǎng)景的過渡绳泉,這里的兩個(gè)場(chǎng)景我們?nèi)∶麨?起始幀結(jié)束幀逊抡。而過渡動(dòng)畫,就是將場(chǎng)景中所有的子視圖從起始幀移動(dòng)到結(jié)束幀的運(yùn)動(dòng)效果零酪。

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

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

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

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


起始幀
結(jié)束幀

這是我寫的兩個(gè)布局文件 scene1.xml 和 scene2.xml 片拍,這個(gè)布局比較簡(jiǎn)單,所以也就不貼了妓肢,需要注意的是捌省,這里只有一層 ViewGroup ,所有 ImageView 都在同一個(gè)層級(jí)下碉钠,且視圖的 id 需要一一對(duì)應(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)于后面兩個(gè)方法,我沒有找到合適使用他們的場(chǎng)景斤寂,但是文檔說明中指出耿焊,這兩個(gè)方法用于沒有使用布局資源或?qū)哟谓Y(jié)構(gòu)定義的場(chǎng)景,或者在這些層次結(jié)構(gòu)更改后需要執(zhí)行附加步驟的場(chǎng)景遍搞。
有點(diǎn)抽象罗侯,之后如果有遇到合適的場(chǎng)景,再看吧溪猿。

四钩杰、Transition
效果圖-g.png

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

1)介紹
Transition 類承載了切換到目標(biāo)場(chǎng)景所有的動(dòng)畫效果依痊,它的子類可以實(shí)現(xiàn)一組動(dòng)畫避除,也可以自定義實(shí)現(xiàn)動(dòng)畫。Transition 有兩個(gè)核心任務(wù):1.記錄特定的屬性胸嘁;2.根據(jù)記錄屬性的變化執(zhí)行過渡動(dòng)畫瓶摆。

2)聲明一個(gè) TransitionSet 對(duì)象
TransitionSet 對(duì)象可以在 xml 文件中初始化,路徑為:res/transition
例如我們創(chuàng)建一個(gè)帶有 explode性宏、changeBounds群井、changeTransform、changeClipBounds衔沼、changeImageTransform 的過渡動(dòng)畫蝌借。

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

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

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

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

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

CustomTransition 是一個(gè)自定義的動(dòng)畫。自定義動(dòng)畫和自定義 View 很像搓劫,它需要繼承 Transition 類瞧哟,并實(shí)現(xiàn)三個(gè)方法:

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

這里枪向,我們簡(jiǎn)單實(shí)現(xiàn)一個(gè)改變布局高度的動(dòng)畫勤揩。
首先在captureStartValues方法中獲取動(dòng)畫起始高度屬性:

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

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

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

最后在createAnimator中創(chuàng)建動(dò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

之后會(huì)再看看把 Share Element 和 Transition 結(jié)合,做頁(yè)面跳轉(zhuǎn)動(dòng)畫秘蛔。

以上陨亡。

感謝:
Material-Animations

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末傍衡,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子负蠕,更是在濱河造成了極大的恐慌蛙埂,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遮糖,死亡現(xiàn)場(chǎng)離奇詭異绣的,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)欲账,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門屡江,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人赛不,你說我怎么就攤上這事盼理。” “怎么了俄删?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)奏路。 經(jīng)常有香客問我畴椰,道長(zhǎng),這世上最難降的妖魔是什么鸽粉? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任斜脂,我火速辦了婚禮,結(jié)果婚禮上触机,老公的妹妹穿的比我還像新娘帚戳。我一直安慰自己,他們只是感情好儡首,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布片任。 她就那樣靜靜地躺著,像睡著了一般蔬胯。 火紅的嫁衣襯著肌膚如雪对供。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天氛濒,我揣著相機(jī)與錄音产场,去河邊找鬼。 笑死舞竿,一個(gè)胖子當(dāng)著我的面吹牛京景,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骗奖,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼确徙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼醒串!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起米愿,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤厦凤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后育苟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體较鼓,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年违柏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了博烂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡漱竖,死狀恐怖禽篱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情馍惹,我是刑警寧澤躺率,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站万矾,受9級(jí)特大地震影響悼吱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜良狈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一后添、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧薪丁,春花似錦遇西、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漫玄,卻和暖如春梧税,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背称近。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工第队, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人刨秆。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓凳谦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親衡未。 傳聞我的和親對(duì)象是個(gè)殘疾皇子尸执,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容

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