一 概述
? ? ? ?Scene 是Android 19 引入的轉(zhuǎn)換框架中一個(gè)場景api,幫我們友好的創(chuàng)建開始布局Scene和結(jié)束布局Scene,有了開始Scene和結(jié)束Scene,運(yùn)用Transition框架來實(shí)現(xiàn)帶有動(dòng)畫的場景切換。舉個(gè)例子,從A布局切換到B布局,一般情況下處理是View.GONE,View.VISIBLE,但是這樣太生硬了,沒有一點(diǎn)過度效果。那么Android的Transition框架就可以完美的解決切換場景帶來的生硬視覺感受。
其中Scene是一個(gè)容器,就是放置你定義的布局,而真正去做場景之間切換這個(gè)動(dòng)作是Transition框架中TransitionManager 調(diào)用其中go方法或者transitionTo方法完成場景之間切換,而真正創(chuàng)建具體動(dòng)畫交由Transition子類來完成,開始動(dòng)畫交給Transition來執(zhí)行。
二 使用

? ? ? ?這里一共有4個(gè)場景,這里先說前3個(gè)。每次切換都帶有移動(dòng)效果。在切換到第三個(gè)場景時(shí),單獨(dú)給第三個(gè)場景中TextView添加了淡入和淡出動(dòng)畫效果。
那么如果讓我們?nèi)?shí)現(xiàn)這樣一個(gè)場景切換,可能會想到在一個(gè)布局中給不同的元素設(shè)置不同的動(dòng)畫,還得監(jiān)聽每個(gè)動(dòng)畫完成后顯示第二個(gè)場景中的元素。這樣寫出來很難閱讀和維護(hù),如果再加一個(gè)元素,又得監(jiān)聽以及顯示和隱藏。
? ? ? ?那么如何實(shí)現(xiàn)前三個(gè)場景切換呢?其實(shí)這三個(gè)場景對應(yīng)三個(gè)layout.xml。每一個(gè)layout.xml對應(yīng)一個(gè)Scene,各自之間不耦合,至于具體動(dòng)畫創(chuàng)建和偏移計(jì)算交給Transition子類來處理,TransitionManager 只是用于做控制流程。

上述中g(shù)if圖代碼如下:
創(chuàng)建布局文件
<!--默認(rèn)布局-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
...
>
<RadioGroup
...>
<RadioButton
android:id="@+id/select_scene_1"
...
>
<RadioButton
android:id="@+id/select_scene_2"
...>
<RadioButton
android:id="@+id/select_scene_3"
...
>
<RadioButton
android:id="@+id/select_scene_4"
...>
</RadioGroup>
<FrameLayout
android:id="@+id/scene_root"
...
>
<include layout="@layout/scene1"/>
</FrameLayout>
</LinearLayout>
<!--場景一 布局文件-->
<RelativeLayout
android:id="@+id/container"
...>
<矩形
android:id="@+id/transition_square"
/>
<箭頭
android:id="@+id/transition_image"
android:layout_below="@id/transition_square"
/>
<圓形
android:id="@+id/transition_oval"
android:layout_below="@id/transition_image"
/>
</RelativeLayout>
<!--場景二 布局文件-->
<RelativeLayout
android:id="@+id/container"
...
>
<View
android:id="@+id/transition_square"
android:layout_alignParentBottom="true"/>
<ImageView
android:id="@+id/transition_image"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"/>
<ImageView
android:id="@+id/transition_oval"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
場景三布局文件這里省略。其實(shí)布局一樣只是各個(gè)View中的位置不一樣,場景三多了一個(gè)TextView,這里暫且先說場景一和場景二。從場景一和場景二可以發(fā)現(xiàn)兩個(gè)場景id是一樣的,只是位置不一樣。其實(shí)場景切換中匹配規(guī)則除了id匹配還有如下匹配規(guī)則:
- instance 匹配同一引用
- transitionName 匹配同一transitionName
- itemId 匹配ListView中adapter id
接下來就是代碼創(chuàng)建Scene,調(diào)用TransitionManager.go()方法開啟場景切換。
創(chuàng)建場景Scene1
ViewGroup mSceneRoot = (ViewGroup) view.findViewById(R.id.scene_root);
Scene mScene1 = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container));
創(chuàng)建場景Scene2
Scene mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, getActivity());
開始動(dòng)畫
...
case R.id.select_scene_1: {
TransitionManager.go(mScene1);
break;
}
case R.id.select_scene_2: {
TransitionManager.go(mScene2);
break;
}
...
這樣就實(shí)現(xiàn)上面gif圖場景一到場景二切換的動(dòng)畫了,場景三這里同理。上面給出創(chuàng)建場景,有兩種方式。
- 從當(dāng)前位置創(chuàng)建Scene
- 通過Scene.getSceneForLayout()方法創(chuàng)建Scene
Scene.getSceneForLayout() 參數(shù)介紹如下:
- sceneRoot 表示從什么地方開始切換場景。
- layoutId 切換到什么場景的布局文件,比如上述例子中生成Scene2實(shí)例,layoutId 就是場景2中的布局。
- context 上下文
上述簡單的例子是通過TransitionManager.go()觸發(fā)動(dòng)畫,go()方法中其實(shí)設(shè)置了默認(rèn)轉(zhuǎn)換動(dòng)畫
private void init() {
setOrdering(ORDERING_SEQUENTIAL);
addTransition(new Fade(Fade.OUT)).
addTransition(new ChangeBounds()).
addTransition(new Fade(Fade.IN));
}
設(shè)置了一個(gè)changeBounds,和Fade轉(zhuǎn)換效果。
類似于ChangeBounds類的還有以下幾種,他們都是繼承Transiton類
- ChangeBounds檢測view的位置邊界創(chuàng)建移動(dòng)和縮放動(dòng)畫
- ChangeTransform檢測view的scale和rotation創(chuàng)建縮放和旋轉(zhuǎn)動(dòng)畫
- ChangeClipBounds檢測view的剪切區(qū)域的位置邊界,和ChangeBounds類似。不過ChangeBounds針對的是view而ChangeClipBounds針對的是view的剪切區(qū)域(setClipBound(Rect rect) 中的rect)。如果沒有設(shè)置則沒有動(dòng)畫效果
- ChangeImageTransform檢測ImageView(這里是專指ImageView)的尺寸,位置以及ScaleType,并創(chuàng)建相應(yīng)動(dòng)畫。
- Fade,Slide,Explode這三個(gè)都是根據(jù)view的visibility的不同分別創(chuàng)建漸入,滑動(dòng),爆炸動(dòng)畫。
上述通過代碼實(shí)現(xiàn)場景轉(zhuǎn)換動(dòng)畫,下面通過xml方式定義一組動(dòng)畫集合。在res/transition/創(chuàng)建xml文件
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds />
<fade android:fadingMode="fade_in_out"/>
</transitionSet>
xml使用
transition = TransitionInflater.from(this).inflateTransition(R.transition.changebounds_fadein_together);
isSwitch = !isSwitch;
TransitionManager.go(isSwitch ? scene2 : scene1, transition);
前面說了場景一和場景二的創(chuàng)建和使用,那么場景三也就so easy 了,可以發(fā)現(xiàn)場景三中多一個(gè)TextView,此TextView需要在切換場景時(shí),淡入淡出,怎么實(shí)現(xiàn)呢?
其實(shí)可以通過TransitionInflater 中inflateTransitionManager方法實(shí)現(xiàn)。
- 定義scene3_transition_manager.xml,
指定場景布局文件,和切換動(dòng)畫。
<transitionManager xmlns:android="http://schemas.android.com/apk/res/android">
<transition
android:toScene="@layout/scene3"
android:transition="@transition/changebounds_fadein_together"/>
</transitionManager>
- 定義changebounds_fadein_together.xml,
指定動(dòng)畫效果和需要新加入的Viewid。此時(shí)為場景三中新加入TextView Id
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
<changeBounds />
<fade android:fadingMode="fade_in_out">
<targets>
<target android:targetId="@id/transition_title" />
</targets>
</fade>
</transitionSet>
其中定義的target標(biāo)簽 targetId 表示只針對于這個(gè)id,還有和它相反的
excludeId 除了這個(gè)id。(excludeId 僅在api21上才可以)
創(chuàng)建場景Scene3
Scene mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene3, getActivity());
加載xml中定義的轉(zhuǎn)換效果
TransitionManager mTransitionManagerForScene3 =TransitionInflater.from(getActivity())
.inflateTransitionManager(R.transition.scene3_transition_manager, mSceneRoot)
調(diào)用
mTransitionManagerForScene3.transitionTo(mScene3)
上述例子中是針對多個(gè)場景切換實(shí)現(xiàn)轉(zhuǎn)換動(dòng)畫,
那么有時(shí)候沒有多個(gè)場景切換,只想改變其中某一個(gè)場景下的某一個(gè)View屬性來實(shí)現(xiàn)過度動(dòng)畫,
可以使用一下api。
TransitionManager.beginDelayedTransition(ViewGroup sceneRoot)
beginDelayedTransition原理是通過代碼改變view的屬性,然后通過之前介紹的ChangeBounds等類分析start scene和end Scene不同來創(chuàng)建動(dòng)畫。
以下例子實(shí)現(xiàn)在一個(gè)場景中特定的View放大效果,其實(shí)也是上面gif圖的場景4了,具體效果,文末給出代碼鏈接,可自行查看。
TransitionManager.beginDelayedTransition(mSceneRoot);
View square = mSceneRoot.findViewById(R.id.transition_square);
ViewGroup.LayoutParams params = square.getLayoutParams();
int newSize=getResources().getDimensionPixelSize(R.dimen.square_size_expanded);
params.width = newSize;
params.height = newSize;
square.setLayoutParams(params);
三 原理
? ? ? ?就拿上述中定義的兩個(gè)場景Scene1 和 Scene2來說, Scene 只是用來保存當(dāng)前場景布局,而真正去創(chuàng)建動(dòng)畫和開始動(dòng)畫的是Transition,TransitionManager 只是用來做控制流程的。
假設(shè)
Scene1-->Scene2
當(dāng)代碼調(diào)用TransitionManager.go(mScene2)時(shí)執(zhí)行流程
? ? ? ?從設(shè)置的mSceneRoot 開始,遍歷Scene1中視圖樹,存儲每次遍歷的View在自己父View中的位置,以及該View中的id作為開始動(dòng)畫時(shí)位置和條件,當(dāng)保存完畢,刪除mSceneRoot 中存在的Scene1添加Scene2監(jiān)聽視圖樹的繪制,當(dāng)繪制完畢遍歷當(dāng)前已添加的Scene2中視圖樹,存儲每次遍歷的View在自己父View中的位置,以及該View中的id作為結(jié)束動(dòng)畫位置和條件。當(dāng)動(dòng)畫的開始位置和動(dòng)畫結(jié)束位置已確定,那么創(chuàng)建動(dòng)畫交給Transition子類,上述例子中用的是Transition的子類 ChangeBounds 。當(dāng)通過結(jié)束位置和開始位置創(chuàng)建動(dòng)畫完畢后,最終通過Transition中runAnimators開啟動(dòng)畫。
參考鏈接: