可以用 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_infade_outfade_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
沒有留言:
張貼留言