之前在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à)

之所以說(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)系:

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è)例子:

而其源碼:
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) 作用。