Android場景動(dòng)畫(Scene)

一 概述


? ? ? ?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í)行。

二 使用

image

? ? ? ?這里一共有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è)場景Scene1Scene2來說, 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)畫完畢后,最終通過TransitionrunAnimators開啟動(dòng)畫。

參考鏈接:

googleDeveloper

BasicTransition

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容