材料設(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);
上面的例子是直接使用 LinearLayout 設(shè)置背景色獲取的方塊废登。當(dāng)然普通 view 本身的大小變化也可以獲得過渡效果,但是如果你使用的是自定義 View 可能你獲得的過渡效果和想象的會(huì)有所不同郁惜。
這里我們用自定義 View 圓點(diǎn)視圖為例堡距,為了讓效果更明顯,我給這個(gè) PointView 的容器添加了背景,獲取效果為:
產(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è)例子:
這是我寫的兩個(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
上面是 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;
}
之后會(huì)再看看把 Share Element 和 Transition 結(jié)合,做頁(yè)面跳轉(zhuǎn)動(dòng)畫秘蛔。
以上陨亡。