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