Android Transition Framework(過渡動(dòng)畫框架)

先說下什么是 Transition(過渡動(dòng)畫). Lollipop(5.0) 中 Activity 和 Fragment 的過渡動(dòng)畫是基于 Android 一個(gè)叫作 Transition 的新特性實(shí)現(xiàn)的。 初次引入這個(gè)特性是在 KitKat(4.4) 中,Transition 框架提供了一個(gè)方便的 API 來構(gòu)建應(yīng)用中不同 UI 狀態(tài)切換時(shí)的動(dòng)畫。 這個(gè)框架始終圍繞兩個(gè)關(guān)鍵概念:場(chǎng)景和過渡。 場(chǎng)景 描述應(yīng)用中 UI 的狀態(tài)(這個(gè)定義太抽象了,下面會(huì)具體解釋),過渡 就是確定兩個(gè)場(chǎng)景轉(zhuǎn)換之間的過渡動(dòng)畫。

具體作用:

  • 可以在界面(Activity & Fragment)之間跳轉(zhuǎn)的時(shí)候添加動(dòng)畫
  • 動(dòng)畫共享元素之間的轉(zhuǎn)換活動(dòng)
  • 界面中布局元素的過渡動(dòng)畫。

1. Transitions between Activity & Fragment(Content Transition)

在 Android 5.0 中, 切換 Activitys 或者 Fragments 時(shí)可以使用 Transitions 來構(gòu)建精致的轉(zhuǎn)場(chǎng)動(dòng)畫。雖然在之前的版本中已經(jīng)引入 Activity 和 Fragment 的切換動(dòng)畫(通過 Activity#overridePendingTransition() 和 FragmentTransaction#setCustomAnimation() 方法實(shí)現(xiàn)),但是動(dòng)畫的對(duì)象只能是Activity/Fragment整體。而新的 API 將這個(gè)特性延伸,可以協(xié)調(diào) Activity/Fragment 中每一個(gè) view ,為其設(shè)置單獨(dú)的的進(jìn)入和退出 transition,輕松搞定流暢的屏幕切換動(dòng)作。

android.transition預(yù)定義了三種過渡動(dòng)畫:Explode,Slide和Fade。

Explode Slide Fade
從中心移入或移出 從邊緣移入或移出 調(diào)整透明度產(chǎn)生漸變

不同的場(chǎng)景下,我們可以為同一個(gè)界面設(shè)置不同效果的過渡動(dòng)畫,這里解釋下場(chǎng)景的意思:

假設(shè) A 和 B 是兩個(gè) Activity,通過 A 來啟動(dòng) B。 A 叫做 "調(diào)用Activity"(調(diào)用 startActivity() 的那個(gè)) B 就是 "被調(diào)用Activity"

根據(jù)上述情景,可以為activity劃分出4種場(chǎng)景的動(dòng)畫:

  • Activity A 的 退出動(dòng)畫(ExitTransition ),即 A 啟動(dòng) B 時(shí) A 中 View 的動(dòng)畫
  • Activity B 的 進(jìn)入動(dòng)畫(EnterTransition ),即 A 啟動(dòng) B 時(shí) B 中 View 的動(dòng)畫
  • Activity B 的 返回動(dòng)畫(ReturnTransition),即 B 返回 A 時(shí) B 中 View 的動(dòng)畫
  • Activity A 的 重入動(dòng)畫(ReenterTransition), 即 B 返回 A 時(shí) A 中 View 的動(dòng)畫

實(shí)現(xiàn)方法

接下來我們實(shí)際演示下為Activity添加過場(chǎng)動(dòng)畫的實(shí)現(xiàn)步驟,可通過xml和代碼兩種方式實(shí)現(xiàn),以添加Fade動(dòng)畫為例:

  1. 如果是xml方式實(shí)現(xiàn),首先在/res下創(chuàng)建transition文件夾。
    res/transition/slide_from_right
<?xml version = 1.0 encoding = "utf-8"?>
<transitionSet xmls:android = "http://schemas.android.com/apk/res/android">

<slide duration = "500"
       slideEage = "left"/>

</transitionSet>

然后在values/styles.xml中創(chuàng)建帶動(dòng)畫的主題,再加到Manifest.xml中就可以了:

<style name="TransitionAppTheme" parent="AppTheme">
        <item name="android:windowContentTransitions">true</item>
        <item name="android:windowEnterTransition">@transition/slide_from_right</item>
        <item name="android:windowExitTransition">@transition/slide_from_right</item>
        <!--避免跳轉(zhuǎn)的Activity動(dòng)畫重疊,則下面兩個(gè)屬性設(shè)為false-->
        <item name="android:windowAllowEnterTransitionOverlap">false</item>
        <item name="android:windowAllowReturnTransitionOverlap">false</item>
    </style>
  1. 如果直接用代碼實(shí)現(xiàn),可以如下實(shí)現(xiàn)
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ... ...
    
    Slide slideTracition = newSlide();
    slideTracition.setSlideEdge(Gravity.LEFT);
    slideTracition.setDuration(getResources().getInteger(R.integer.anim_duration_long));

    //也可以直接取res中的transition資源
    //Transition slideTracition = TransitionInflater.from(this).inflateTransition(R.transition.slide_from_left);
    getWindow().setEnterTransition(slideTracition);
    getWindow().setExitTransition(slideTracition);
    
     ... ...
 }

最后,啟動(dòng)動(dòng)畫

//跳轉(zhuǎn)
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle()
startActivity(i, bundle);
//返回
finshAfterTransition();

上面我們只添加了EnterTransition和ExitTransition,如果未設(shè)置ReturnTransition和ReenterTransition的話,后兩者分別為前兩者的反向動(dòng)畫。
EnterTransition < - > ReturnTransition
ExitTransition < - > ReenterTransition

2.Share elements between Activity(元素共享)

共享元素過渡動(dòng)畫的背后是通過過渡動(dòng)畫將兩個(gè)不同布局中的不同view關(guān)聯(lián)起來。Transition框架知道用適當(dāng)?shù)膭?dòng)畫向用戶展示從一個(gè)view向另外 一個(gè)view過度。請(qǐng)記?。汗蚕碓剡^渡的過程中,view并沒有真正從一個(gè)布局跑到另外一個(gè)布局,整個(gè)過程基本都是在后一個(gè)布局中完成的。


image

元素共享的實(shí)現(xiàn)

  1. 允許過渡動(dòng)畫
    需要在/res/style.xml添加
<item name="android:windowContentTransitions">true</item>
  1. 在對(duì)應(yīng)的xml文件指定TransitionName屬性
    例如,我們指定activityA中的ImageView和activityB中的TextView為共享元素:
<ImageView
        android:id="@+id/square_blue"
        style="@style/MaterialAnimations.Icon.Big"
        android:src="@drawable/circle_24dp"
        android:transitionName="@string/square_blue_name" />
<TextView
       android:id="@+id/title"   
       style="@style/MaterialAnimations.TextAppearance.Title.Invers"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_gravity="center_vertical|start"
       android:text="@{sharedSample.name}"
       android:transitionName="@string/sample_blue_title" />

注意:這里必須為共享的倆個(gè)元素指定同一個(gè)TransitionName,不然不會(huì)出現(xiàn)共享效果

  1. 啟動(dòng)Activity
Intent intent = new Intent(activity,target);
ActivityOptionCompat option = ActiviyoptionCampat.makeSceneTransitionAnimation(activity,  
new Pair<View, String>(viewHolder.binding.sampleIcon, activity.getString(R.string.square_blue_name)),
new Pair<View, String>(viewHolder.binding.sampleName, activity.getString(R.string.sample_blue_title)));

startActivity(intent,option.toBundle());

效果如下:



?

共享元素動(dòng)畫的效果設(shè)置

我們可以發(fā)現(xiàn)對(duì)于轉(zhuǎn)場(chǎng)動(dòng)畫(Content transitions) 是根據(jù)每個(gè)過渡視圖的可見性變化來調(diào)節(jié)的,而共享元素 transition 是根據(jù)每個(gè)共享元素視圖的位置,大小和外觀的變化來調(diào)節(jié)的。與轉(zhuǎn)場(chǎng)動(dòng)畫類似,從 API 21 開始,框架提供了 幾個(gè)自定義共享元素場(chǎng)景切換動(dòng)畫的 Transition 實(shí)現(xiàn)。

  • ChangeBounds - 捕獲共享元素布局邊界根據(jù)不同構(gòu)造動(dòng)畫。 ChangeBounds 在共享元素 Transition 中經(jīng)常使用,大多數(shù)共享元素在兩個(gè) Activity/Fragment 間會(huì)有大小 或/和 位置不同。
  • ChangeTransform - 捕獲共享元素縮放和角度, 根據(jù)不同構(gòu)建動(dòng)畫。
  • ChangeClipBounds - 捕獲共享元素的 clip bounds (剪輯邊界) ,根據(jù)不同構(gòu)建動(dòng)畫。
  • ChangeImageTransform - 捕獲共享元素 ImageView 的 變換矩陣( transform matrices) ,根據(jù)不同構(gòu)建動(dòng)畫。結(jié)合 ChangeBounds, 可以讓 ImageView 無縫的改變大小,形狀和 ImageView.ScaleType 。

設(shè)置代碼:

Slide slide = new Slide();
slide.setDuration(500);

ChangeBounds changeBounds = new ChangeBounds();
changeBounds.setDuration(500);

getWindow().setEnterTransition(slide);
getWindow().setSharedElementEnterTransition(changeBounds);

前面的代碼我們并沒有設(shè)置共享元素動(dòng)畫,因?yàn)槿绻覀兊腡heme使用的是 Material主題,當(dāng)設(shè)置共享元素后會(huì)默認(rèn)添加動(dòng)畫效果。

過渡動(dòng)畫以及共享元素在Fragment之間的實(shí)現(xiàn)

fragment使用過渡動(dòng)畫和共享元素,與Activity大同小異,也是直接添加Transition對(duì)象之后啟動(dòng),主要是啟動(dòng)方法有所不同

private void addNextFragment(Sample sample, ImageView blue, boolean b) {
        SharedElementFragment2 elementFragment2 = SharedElementFragment2.newInstance(sample);
        Slide slide = new Slide();
        slide.setDuration(getResources().getInteger(R.integer.anim_duration_medium));
        slide.setSlideEdge(Gravity.RIGHT);
        
        ChangeBounds changeBounds = new ChangeBounds();
        changeBounds.setDuration(getResources().getInteger(R.integer.anim_duration_medium));
        
        elementFragment2.setEnterTransition(slide);
        elementFragment2.setAllowEnterTransitionOverlap(b);
        elementFragment2.setAllowReturnTransitionOverlap(b);
        elementFragment2.setSharedElementEnterTransition(changeBounds);

        getFragmentManager().beginTransaction().replace(R.id.sample2_content, elementFragment2).addToBackStack(null).addSharedElement(blue, getString(R.string.square_blue_name)).commit();
    }

效果如下:


3.TransitionManager 控制動(dòng)畫

TransitionManager是個(gè)很好用的工具,使用TransitionManager,我們給view添加一些簡(jiǎn)單屬性動(dòng)畫的時(shí)候只需要得到這個(gè)view的根布局,然后設(shè)置下view動(dòng)畫之后的狀態(tài)就可以了,TransitionManager會(huì)自動(dòng)為每個(gè)view添加預(yù)設(shè)置好的屬性動(dòng)畫,我們甚至可以用它對(duì)一個(gè)布局內(nèi)的一組多個(gè)view一塊兒添加動(dòng)畫,從而實(shí)現(xiàn)較為復(fù)雜的聯(lián)動(dòng)效果。事實(shí)上UI場(chǎng)景切換的效果就是通過它來實(shí)現(xiàn)的。

public class ExampleActivity extends Activity implements View.OnClickListener {
    private ViewGroup mRootView;
    private View mRedBox, mGreenBox, mBlueBox, mBlackBox;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //mRootView是要添加動(dòng)畫view的根布局
        mRootView = (ViewGroup) findViewById(R.id.layout_root_view);
        mRootView.setOnClickListener(this);

        mRedBox = findViewById(R.id.red_box);
        mGreenBox = findViewById(R.id.green_box);
        mBlueBox = findViewById(R.id.blue_box);
        mBlackBox = findViewById(R.id.black_box);
    }

    @Override
    public void onClick(View v) {
        TransitionManager.beginDelayedTransition(mRootView, new Fade());
        toggleVisibility(mRedBox, mGreenBox, mBlueBox, mBlackBox);
    }

    private static void toggleVisibility(View... views) {
        for (View view : views) {
            boolean isVisible = view.getVisibility() == View.VISIBLE;
            view.setVisibility(isVisible ? View.INVISIBLE : View.VISIBLE);
        }
    }
}

上面的例子我們?yōu)橐唤M四個(gè)色塊添加了Fade效果的動(dòng)畫,同樣可以設(shè)置成Slide或者Explode,效果如下:


對(duì)于一些更為復(fù)雜的聯(lián)動(dòng)動(dòng)畫,TransitionManager還為我們提供了一種類似于切換布局就可以完成動(dòng)畫的方案,極大的簡(jiǎn)化了我們實(shí)現(xiàn)復(fù)雜動(dòng)畫

實(shí)現(xiàn)步驟如下:

  1. 定義需要切換 layout xml頁面;
  2. 調(diào)用 Scene.getSceneForLayout() 保存每個(gè)Layout;
  3. 調(diào)用 TransitionManager.go(scene1, new ChangeBounds()) 切換。
    代碼示例:
private void setupLayout() {
    scene0 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene0, this);
    scene1 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene1, this);
    scene2 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene2, this);
    scene3 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene3, this);
    scene4 = Scene.getSceneForLayout(binding.sceneRoot, R.layout.activity_animations_scene4, this);
    binding.sample3Button1.setOnClickListener(this);
    binding.sample3Button2.setOnClickListener(this);
    binding.sample3Button3.setOnClickListener(this);
    binding.sample3Button4.setOnClickListener(this);
}

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.sample3_button1:
            TransitionManager.go(scene1, new ChangeBounds());
            break;
        case R.id.sample3_button2:
            TransitionManager.go(scene2, TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds));
            break;
        case R.id.sample3_button3:
            TransitionManager.go(scene3,TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential));
            break;
        case R.id.sample3_button4:
            TransitionManager.go(scene3,TransitionInflater.from(this).inflateTransition(R.transition.slide_and_changebounds_sequential_with_interpolators));
            break;
    }
}

4.CircularReveal 顯示或隱藏 的效果

ViewAnimationUtils.createCircularReveal()
當(dāng)您顯示或隱藏一組 UI 元素時(shí),Circular Reveal 可為用戶提供視覺連續(xù)性



參考說明:

Animator createCircularReveal (View view, // 將要變化的 View
            int centerX,                  // 動(dòng)畫圓的中心的x坐標(biāo)
            int centerY,                  // 動(dòng)畫圓的中心的y坐標(biāo)
            float startRadius,            // 動(dòng)畫圓的起始半徑
            float endRadius               // 動(dòng)畫圓的結(jié)束半徑
)

顯示View:

private void animShow() {
    View myView = findViewById(R.id.my_view);
    // 從 View 的中心開始
    int cx = (myView.getLeft() + myView.getRight()) / 2;
    int cy = (myView.getTop() + myView.getBottom()) / 2;
    int finalRadius = Math.max(myView.getWidth(), myView.getHeight());

    //為此視圖創(chuàng)建動(dòng)畫設(shè)計(jì)(起始半徑為零)
    Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
    // 使視圖可見并啟動(dòng)動(dòng)畫
    myView.setVisibility(View.VISIBLE);
    anim.start();
}

隱藏View:

private void animHide() {
    final View myView = findViewById(R.id.my_view);
    int cx = (myView.getLeft() + myView.getRight()) / 2;
    int cy = (myView.getTop() + myView.getBottom()) / 2;

    int initialRadius = myView.getWidth();

    // 半徑 從 viewWidth -> 0
    Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);

    anim.addListener(new AnimatorListenerAdapter() {
        @Override
        public void onAnimationEnd(Animator animation) {
            super.onAnimationEnd(animation);
            myView.setVisibility(View.INVISIBLE);
        }
    });
    anim.start();
}

以上效果都只支持 API 23 以上,所以在我們常用的 APP 中都還不常見,但是效果真的很不錯(cuò),很值得研究下。

參考及拓展閱讀:
在 Activity 和 Fragment 中使用 Transition (part 1)
深入理解Content Transition (part 2)
深入理解 Shared Element Transition (part 3a)
延遲共享元素的過渡動(dòng)畫 (part 3b)
Android共享元素轉(zhuǎn)場(chǎng)動(dòng)畫兼容實(shí)踐
【Transition】Android炫酷的Activity切換效果,共享元素

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

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

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