可以用 Property Animation 去做
Android 4.0 (API level 14)後也可以在 xml 裡使用 android:animateLayoutChanges="true" 這樣的方式來做
但 4.4.2 KitKat (API level 19) 新增了 Scene Transition 的功能
從官方範例 Basic Transition 來看非常的簡單好用
以下就簡單介紹一下
mSceneRoot = (ViewGroup) view.findViewById(R.id.scene_root); // 用 new 的方式建立 Scene 物件 // (這個建構子在 API level 21 起已經被標為 deprecated, Lollipop 以後應該用 Scene(ViewGroup, View) 代替). mScene1 = new Scene(mSceneRoot, (ViewGroup) mSceneRoot.findViewById(R.id.container)); // 也可以呼叫Scene.getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context) // 從 layout 的 xml 來建立 scene mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, getActivity()); mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene3, getActivity()); // 自訂 TransitionManager 來設定轉場效果 mTransitionManagerForScene3 = TransitionInflater.from(getActivity()).inflateTransitionManager( R.transition.scene3_transition_manager, SceneRoot );
/res/transition/scene3_transition_manager.xml
<transitionManager xmlns:android="http://schemas.android.com/apk/res/android"> <transition android:toScene="@layout/scene3" android:transition="@transition/changebounds_fadein_together"/> </transitionManager>
/res/transition/changebounds_fadein_together.xml
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"> <changeBounds/> <fade android:fadingMode="fade_in"> <targets> <target android:targetId="@id/transition_title" /> </targets> </fade> </transitionSet>上面的寫法動畫會同時發生
如果要照順序的話可以在transitionSet tag 加上 android:transitionOrdering="sequential" 屬性
(TransitionSet 類別裡 ORDERING_SEQUENTIAL = 1, ORDERING_TOGETHER = 0, 所以沒設這屬性的話預設是 together)
進行轉場的方法
在不同的 Scene 中若有 id 相同的元件,Transition就會自動產生動畫去改變位置及大小
若有新增或消失的元件,則會 fade in/fade out
內建的 Transition:
Class | Tag | Attributes | Effect |
---|---|---|---|
AutoTransition | <autoTransition/> | - | 預設的 transition. 按順序執行 Fade out -> move -> resize -> fade in |
Fade | <fade/> | android:fadingMode= | fade_in fade_out fade_in_out (預設) 先fade_out 再 fade_in |
ChangeBounds | <changeBounds/> | - | Moves and resizes views. |
不過 transition 對一些元件是無法正常作用的:
SurfaceView - SurfaceView 是在 Non-UI thread 更新的
TextureView - 有些動畫無法套用
繼承 AdapterView 的類別(如 ListView) - AdapterView 有其管理 child 的方式, 如果套用 transition 可能會導致畫面無反應
TextView - 無法動畫呈現 resize
可以呼叫 public Transition removeTarget (target) 方法來把不想要進行轉場動畫的或是上述這些無法正常作用的 view 從 transition 給移除
target 參數可以是 Class, View, int targetId, String targetName
另外還有
public Transition excludeTarget (target, boolean exclude)
public Transition addTarget (target)
感覺使用上還蠻自由的
// 可以用 TransitionManager.go() 讓轉場自動進行 TransitionManager.go(mScene2); // TransitionManager.go 有一個 overload 的方法 可以傳入指定的 transition // public static void go (Scene scene, Transition transition) // 或是用剛剛自訂的 TransitionManager 呼叫 transitionTo(Scene scene) mTransitionManagerForScene3.transitionTo(mScene3); // 轉場也可以不靠 Scene 來進行 // 這裡先呼叫 TransitionManager.beginDelayedTransition(final ViewGroup sceneRoot) // 他會把目前 View 的狀態跟屬性記下來 // 然後就開始改變 View 吧, TransitionManager 也會把新的狀態記下來 // 當系統 redraw UI的時候動畫就會進行了 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 = params.height = newSize; square.setLayoutParams(params); // 也可以用 ViewGroup.removeView() 和 ViewGroup.addView() 來移除/增加 View mLabelText = new TextView(); mLabelText.setText("Label").setId("1"); mSceneRoot.addView(mLabelText);
Transition 生命週期事件可以透過 android.transition.Transition.TransitionListener 監聽
onTransitionStart(Transition transition)
onTransitionCancel(Transition transition)
onTransitionPause(Transition transition)
onTransitionResume(Transition transition)
onTransitionEnd(Transition transition)
另外如果在轉場前後有需要利用 callback 做些事情
可以透過 Scene.setExitAction(Runnable action) 和 public void setEnterAction (Runnable action) 這兩個方法來定義
starting scene 的 setExitAction(Runnable action) 會在轉場執行前被呼叫
ending scene 的 setEnterAction (Runnable action) 會在轉場完成後被呼叫
不過官方強調請不要用這兩個方法來在場景間傳遞資料...
正確方式是把 View 的 data 先存起來,等到 transition 完成以後會觸發 onTransitionEnd(Transition transition),這時再把 data 塞回 View
2015/9/11 更新:
由於 scene 轉換後,裡面的 view 跟原本的不會是同一個 instance
這樣會導致原本設定的 listener 都無法正常動作(如 OnclickListener)
而官方的簡單範例當然沒有關於這部分的 code
所以我自己想了一些解決的方法:
方法一 - 將事件寫在 xml 裡
android:onClick="onMyViewClicked"這方法限制很多,首先這必須是簡單而且官方已經有的事件,其次必須在 Activity 裡建立相對應的 onMyViewClicked(View) 方法
所以這解法基本上並不好用
方法二 - 將 listener 的指派放在 TransitionManager.go(mEndingScene) 之後
這樣問題可以解決且很直接,但是這樣 code 看起來不美觀且有點繁雜(個人對程式碼有點龜毛...)
所以我最後把方法二改良成方法三 - 利用 Transition 的 onTransitionEnd 來做
好處是 view 的 listener 指派都集中在一個方法
要用哪個解法就看各人自由選擇嚕
AutoTransition defaultTransition; private Scene mCurrentScene; private void initViewListener() { //將 view 的 listener 指派都集中在一個方法裡設定 View square = mSceneRoot.findViewById(R.id.transition_square); //由於有的 view 可能已經不在畫面上,所以還是先判斷是否為 null 比較保險 //(當然也可以用目前所在的 scene 來判斷畫面上會有那些 view,不過那樣寫會比較多 if 判斷,個人不喜歡) if(square != null) { square.setOnClickListener(onSquareClick); } View oval = mSceneRoot.findViewById(R.id.transition_oval); if(oval != null) { oval.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 省略... } }); } } Transition.TransitionListener transitionListener = new Transition.TransitionListener() { @Override public void onTransitionStart(Transition transition) { } @Override public void onTransitionEnd(Transition transition) { initViewListener(); //在轉場完成後進行 listener 指派 } @Override public void onTransitionCancel(Transition transition) { } @Override public void onTransitionPause(Transition transition) { } @Override public void onTransitionResume(Transition transition) { } }; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) { // 省略... //在 onCreateView 將 Transition 給 new 出來並加上 transitionListener //這裡我使用預設動作的 AutoTransition defaultTransition = new AutoTransition(); defaultTransition.addListener(transitionListener); // 省略... // TransitionManager 的 instance 可以透過 setTransition(scene, transition) 為場景指定 transition mTransitionManagerForScene3 = TransitionInflater.from(getActivity()) .inflateTransitionManager(R.transition.scene3_transition_manager, mSceneRoot); mTransitionManagerForScene3.setTransition(mScene3, defaultTransition); } public void switchScene(int sceneIndex) { switch (sceneIndex) { case 1: // 傳入 transition 參數指定使用已經加了 listener 的 transition // (這裡統一都用 defaultTransition) TransitionManager.go(mScene1, defaultTransition); mCurrentScene = mScene1; //如果同一個 id 的 view 在不同 scene 要有不同動作的話,可以把目前的 scene 記住 break; case 2: TransitionSet tranSet = new TransitionSet(); tranSet.setOrdering(TransitionSet.ORDERING_TOGETHER); tranSet.addTransition(defaultTransition); CustomTrans customTrans = new CustomTrans(); tranSet.addTransition(customTrans); //tranSet.addListener(transitionListener); //TransitionSet 可以直接加 listener 但由於我們已經在 defaultTransition 加了,不需要重複做 //(TransitionSet 與裡面單一 Transition 的 onTransitionEnd 在意義上是不同的,雖然在本例中是沒差,但還是應該注意) TransitionManager.go(mScene2, tranSet); mCurrentScene = mScene2; //如果同一個 id 的 view 在不同 scene 要有不同動作的話,可以把目前的 scene 記住 break; case 3: //前面已經在 TransitionManager 實體將 mScene3 的 transition 指定為加了 listener 的 defaultTransition //這裡直接用 transitionTo mTransitionManagerForScene3.transitionTo(mScene3); mCurrentScene = mScene3; //如果同一個 id 的 view 在不同 scene 要有不同動作的話,可以把目前的 scene 記住 break; case 4: //由於 listener 是監聽 Transition 的狀態,所以不轉換 scene 的 transition 也可以用 TransitionManager.beginDelayedTransition(mSceneRoot, defaultTransition); // 省略... break; } } // 省略...
相關資料:
Android sample code - Basic Transition
Animating Views Using Scenes and Transitions
YouTube - DevBytes: Android 4.4 Transitions
沒有留言:
張貼留言