Activity轉(zhuǎn)場動(dòng)畫

在開始之前吐槽下簡書markdown竟然不支持生成目錄列表,弄半天沒弄出來,如果哪位知道,煩請告知。這里就簡單的截圖一下目錄,講究著看吧....

1. 轉(zhuǎn)場動(dòng)畫

轉(zhuǎn)場動(dòng)畫就是Activity通過元素之間的轉(zhuǎn)換提供不同狀態(tài)之間的視覺連接。你可以為進(jìn)入和退出轉(zhuǎn)換以及Activity之間共享元素的轉(zhuǎn)換指定定制動(dòng)畫

1.1 Api21之前如何實(shí)現(xiàn)轉(zhuǎn)場動(dòng)畫?

Api21之前我們實(shí)現(xiàn)轉(zhuǎn)場動(dòng)畫有兩種方式。

1.1.1 使用 overridePendingTransition

進(jìn)入動(dòng)畫

    startActivity(intent);
    overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);

退出動(dòng)畫

    finish();
    overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);

注意:

1.overridePendingTransition方法必須在startActivity()或者finish()方法的后面。
2.如果參數(shù)是0,表示沒有動(dòng)畫。

1.1.2 使用 Activity主題Style配置

假設(shè)有兩個(gè)Activity AB

A->B activityOpenEnterAnimation

B->A activityOpenExitAnimation

B退出A從新進(jìn)入 activityCloseEnterAnimation

A退出 activityCloseExitAnimation

主題配置

    <style name="AppThisTheme" parent="Theme.AppCompat.Light.NoActionBar">
        ...
        <item name="android:windowAnimationStyle">@style/activityAnimation</item>
    </style>

    <style name="activityAnimation" parent="@android:style/Animation.Activity">
        <item name="android:activityOpenEnterAnimation">@anim/slide_right_in</item>
        <item name="android:activityOpenExitAnimation">@anim/slide_left_out</item>
        <item name="android:activityCloseEnterAnimation">@anim/slide_left_in</item>
        <item name="android:activityCloseExitAnimation">@anim/slide_right_out</item>
    </style>

1.2 Api21之后如何實(shí)現(xiàn)轉(zhuǎn)場動(dòng)畫?

在說Api21之后實(shí)現(xiàn)轉(zhuǎn)場動(dòng)畫之前先來看一張圖,來理清跳轉(zhuǎn)Activity之間跳轉(zhuǎn)設(shè)置的動(dòng)畫方法。

1.2.1 Activity轉(zhuǎn)換動(dòng)畫原理

image

從圖中可以看到Activity之間跳轉(zhuǎn)可以有4個(gè)不同動(dòng)作。
一般如果沒有特殊需求,指定兩個(gè)就可以了,
exitTransitionenterTransition。 設(shè)置進(jìn)入和退出動(dòng)畫時(shí),在進(jìn)行returnTransition時(shí),如果沒有設(shè)置就會(huì)用renterTransition動(dòng)畫設(shè)置的值動(dòng)作相反,同理在進(jìn)行reenterTransition時(shí),如果沒有設(shè)置就會(huì)用exitTransition動(dòng)畫設(shè)置的值動(dòng)作相反。

上面四個(gè)動(dòng)作其實(shí)是View INVISIBLEVISIBLE 或者 VISIBLEINVISIBLE 轉(zhuǎn)換過程。

exitTransition: A退出錢先獲取試圖為VISIBLE場景,設(shè)置試圖為INVISIBLE獲取INVISIBLE場景,根據(jù)transition差異的不同創(chuàng)建執(zhí)行動(dòng)畫。

enterTransition:B進(jìn)入時(shí)會(huì)把B中試圖設(shè)置為INVISIBLE獲取INVISIBLE 場景,然后將視圖設(shè)置為VISIBLE,獲取VISIBLE時(shí)的場景,根據(jù)transition差異的不同創(chuàng)建執(zhí)行動(dòng)畫。

同理returnTransitionrenterTransition 原理一樣。

根據(jù)上面描述Activity場景轉(zhuǎn)換動(dòng)畫時(shí)建立在Visibility基礎(chǔ)上,支持Visibility有三種:

explode:爆炸效果(將視圖從場景的中心移出或移出)

slide:移動(dòng)效果(將視圖從場景的一個(gè)邊緣移動(dòng)或移出,類似于目前項(xiàng)目中常用的activity切換動(dòng)畫,從右邊進(jìn),從左邊出。Slide還支持上面和下面進(jìn)出)

fade :淡入淡出 。

Api21之后實(shí)現(xiàn)轉(zhuǎn)場動(dòng)畫也有兩種方式

1.2.2 使用 Activity主題Style配置

 <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        ...
        <!--必須制定該屬性不然動(dòng)畫不起作用-->
        <item name="android:windowActivityTransitions">true</item>
        <!--activity進(jìn)入動(dòng)畫-->
         <item name="android:windowEnterTransition">@transition/slide_right</item>
        <!--activity退出動(dòng)畫-->
         <item name="android:windowExitTransition">@transition/slide_left</item>
         <!--是否同時(shí)執(zhí)行,如果同時(shí)執(zhí)行A頁面動(dòng)畫還沒退出,B頁面已經(jīng)開始動(dòng)畫,感覺不是很和諧-->
         <item name="android:windowAllowReturnTransitionOverlap">false</item>
        <item name="android:windowAllowEnterTransitionOverlap">false</item>
  </style>
 

注意:

  1. 如果使用主題繼承自android:Theme.Material系列主題,則無需指定windowActivityTransitions,默認(rèn)windowActivityTransitions屬性為true。
  2. 使用主題指定activity進(jìn)入和退出動(dòng)畫創(chuàng)建的xml 在 res/transition/ 文件下。
  3. 啟動(dòng)一個(gè)activity應(yīng)使用兼容方式啟動(dòng)
    ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);
                ActivityCompat.startActivity(MainActivity.this,
                        new Intent(MainActivity.this, TransitionSlideActivity.class), optionsCompat.toBundle());

啟動(dòng)一個(gè)Activity使用Api21之前的方法是沒有任何效果轉(zhuǎn)換動(dòng)畫效果的。

4.退出一個(gè)Activity使用兼容方式退出

  ActivityCompat.finishAfterTransition(TransitionSlideActivity.this);

/res/transition/slide_left 文件內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:duration="@android:integer/config_shortAnimTime"
    android:slideEdge="left">
    <targets>
        <target
            android:excludeId="@android:id/statusBarBackground"
            tools:targetApi="lollipop" />
    </targets>

</slide>

/res/transition/slide_right 文件內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:duration="@android:integer/config_shortAnimTime"
    android:slideEdge="right">
    <targets>
        <target
            android:excludeId="@android:id/statusBarBackground"
            tools:targetApi="lollipop" />
    </targets>
</slide>

duration:表示動(dòng)畫時(shí)長

slideEdge:表示從哪邊進(jìn)入或退出取值有l(wèi)eft|top|right|bottom|

targets:標(biāo)記作用,例如上面例子中標(biāo)記排除系統(tǒng)狀態(tài)欄,其他View都應(yīng)用于轉(zhuǎn)場動(dòng)畫中。除了 排除 某個(gè)View,還有targetId 只針對某個(gè)View,其他View都不作用于轉(zhuǎn)場動(dòng)畫。同理explodefade也支持排除和針對某個(gè)View。

slide|explode|fade 之間還可以兩兩組合,比如退出的動(dòng)畫使用從左邊退出和淡出。

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:duration="@android:integer/config_longAnimTime"
    android:transitionOrdering="together">
    <fade android:fadingMode="fade_in_out" />
    
    <slide android:slideEdge="right" />
    
    <targets>
        <target
            android:excludeId="@android:id/statusBarBackground"
            tools:targetApi="lollipop" />
    </targets>
</transitionSet>

效果圖如下:

image

<center> 右進(jìn)左出并且?guī)в械龅胄Ч?lt;/center >

1.2.3 代碼設(shè)置轉(zhuǎn)場動(dòng)畫

Activity A:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setupWindowAnimations();
        }
        findViewById(R.id.btn_transition_slide_animation).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this);
                ActivityCompat.startActivity(MainActivity.this,
                        new Intent(MainActivity.this, TransitionSlideActivity.class), optionsCompat.toBundle());
            }
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void setupWindowAnimations() {
        TransitionSet transitionSet = new TransitionSet();
        transitionSet.setDuration(300);
        //一起動(dòng)畫
        transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
        Slide slideTransition = new Slide();
        slideTransition.setSlideEdge(Gravity.LEFT);
        transitionSet.addTransition(slideTransition);
        Fade fadeTransition = new Fade();
        transitionSet.addTransition(fadeTransition);
        //排除狀態(tài)欄
        transitionSet.excludeTarget(android.R.id.statusBarBackground, true);
        //是否同時(shí)執(zhí)行
        getWindow().setAllowEnterTransitionOverlap(false);
        getWindow().setAllowReturnTransitionOverlap(false);
        //退出這個(gè)界面
        getWindow().setExitTransition(slideTransition);
    }

Activity B:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_slide_transition);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setupWindowAnimations();
        }
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
              ActivityCompat.finishAfterTransition(TransitionSlideActivity.this);
            }
        });
    }


    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void setupWindowAnimations() {
        TransitionSet transitionSet = new TransitionSet();
        transitionSet.setDuration(300);
        //一起動(dòng)畫
        transitionSet.setOrdering(TransitionSet.ORDERING_TOGETHER);
        Slide slideTransition = new Slide();
        slideTransition.setSlideEdge(Gravity.RIGHT);
        transitionSet.addTransition(slideTransition);
        Fade fadeTransition = new Fade();
        transitionSet.addTransition(fadeTransition);
        //排除狀態(tài)欄
        transitionSet.excludeTarget(android.R.id.statusBarBackground, true);
        //是否同時(shí)執(zhí)行
        getWindow().setAllowEnterTransitionOverlap(false);
        getWindow().setAllowReturnTransitionOverlap(false);
        //進(jìn)入
        getWindow().setEnterTransition(slideTransition);
    }

具體explode效果代碼和效果圖就不貼了可以自己去嘗試看看。

2. 共享元素動(dòng)畫

在說Activity之間共享動(dòng)畫之前還是來一張官方圖

image

2.1 描述

共享動(dòng)畫是分析兩個(gè)界面共享view的尺寸,位置,樣式的不同創(chuàng)建動(dòng)畫化的。從上圖看出Activity1到Activity2,Android小機(jī)器人是一個(gè)被共享的元素。由于兩個(gè)頁面共享元素尺寸位置不一樣,所以實(shí)現(xiàn)效果就是Activity1小機(jī)器人被放大然后顯示在Activity2上,當(dāng)然還可以有Fade效果。關(guān)于尺寸改變動(dòng)畫可以使用ChangeBounds,另外還有
ChangeTransform,ChangeClipBounds以及ChangeImageTransform。

關(guān)于四種ChangeXXX解釋如下:

  1. ChangeBounds:檢測view的位置邊界創(chuàng)建移動(dòng)和縮放動(dòng)畫
  2. ChangeTransform:檢測view的scale和rotation創(chuàng)建縮放和旋轉(zhuǎn)動(dòng)畫
  3. ChangeClipBounds:檢測view的剪切區(qū)域的位置邊界,和ChangeBounds類似。不過ChangeBounds針對的是view而ChangeClipBounds針對的是view的剪切區(qū)域(setClipBound(Rect rect) 中的rect)。如果沒有設(shè)置則沒有動(dòng)畫效果
  4. ChangeImageTransform:檢測ImageView(這里是專指ImageView)的尺寸,位置以及ScaleType,并創(chuàng)建相應(yīng)動(dòng)畫。

說那么多不如來張上面效果圖的動(dòng)圖:

image

2.2 實(shí)現(xiàn)共享動(dòng)畫

一般情況下兩個(gè)Activity ShareElementActivityShareElement1Activity ,假如ShareElementActivity->ShareElement1Activity,配置ShareElement1Activity 中 進(jìn)入的共享動(dòng)畫,ShareElementActivity中配置退出轉(zhuǎn)場動(dòng)畫即可。

假設(shè)兩個(gè)Activity:ShareElementActivityShareElement1Activity

ShareElementActivity 示例代碼

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share_element);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setupWindowAnimations();
        }
        final ImageView shareElement = findViewById(R.id.iv_share_element);
        shareElement.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                  //共享shareElement這個(gè)View
                ActivityOptionsCompat activityOptionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(ShareElementActivity.this, shareElement,
                        "shareElement");
                ActivityCompat.startActivity(ShareElementActivity.this,
                        new Intent(ShareElementActivity.this, ShareElement1Activity.class), activityOptionsCompat.toBundle());
            }
        });

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               ActivityCompat.finishAfterTransition(ShareElementActivity.this);
            }
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void setupWindowAnimations() {
        //爆炸效果進(jìn)入進(jìn)出
        Explode explodeTransition = new Explode();
        explodeTransition.setDuration(300);
        //排除狀態(tài)欄
        explodeTransition.excludeTarget(android.R.id.statusBarBackground, true);
        //是否同時(shí)執(zhí)行
        getWindow().setAllowEnterTransitionOverlap(false);
        getWindow().setAllowReturnTransitionOverlap(false);
        //進(jìn)入
        getWindow().setEnterTransition(explodeTransition);
    }

上述代碼是從ShareElementActivity到ShareElement1Activity 其中共享的View是
shareElement,transitionName是shareElement,這個(gè)transitionName是開啟共享動(dòng)畫的重要因素,通過,transitionName 指定的值來匹配下個(gè)頁面共享的View。

ShareElement1Activity 示例代碼

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_share_element1);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            setupWindowAnimations();
        }

        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        toolbar.setNavigationOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
               ActivityCompat.finishAfterTransition(ShareElement1Activity.this);
            }
        });
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private void setupWindowAnimations() {
        ChangeBounds changeBounds = new ChangeBounds();
        changeBounds.setDuration(300);
        //排除狀態(tài)欄
        changeBounds.excludeTarget(android.R.id.statusBarBackground, true);
        //是否同時(shí)執(zhí)行
        getWindow().setAllowEnterTransitionOverlap(false);
        getWindow().setAllowReturnTransitionOverlap(false);
        //進(jìn)入
        getWindow().setEnterTransition(changeBounds);
    }

    @Override
    public void onBackPressed() {
       ActivityCompat.finishAfterTransition(ShareElement1Activity.this);
    }

可以看到幾乎都類似,只不過兩個(gè)Activity中進(jìn)入退出動(dòng)畫不一樣,需要注意的地方就是返回該頁面不能用finish,而是用finishAfterTransition,不然動(dòng)畫不起作用,還有在ShareElement1Activity的布局文件中必須制定共享View的transitionName屬性。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".share.ShareElement1Activity">
    ...

    <ImageView
        android:id="@+id/iv_share_element1"
        ...
        android:transitionName="shareElement"
        tools:targetApi="lollipop" />
        ...
</RelativeLayout>

如果實(shí)現(xiàn)多個(gè)共享View動(dòng)畫使用以下偽代碼獲取ActivityOptions

ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(this,
        Pair.create(view1, "transitionName1"),
        Pair.create(view2, "transitionName2"));

當(dāng)然Activity設(shè)置主題屬性也可以實(shí)現(xiàn)共享動(dòng)畫只需在該Activity主題中配置

<item name="android:windowSharedElementEnterTransition">..</item>
<item name="android:windowSharedElementExitTransition">...</item>

3. 實(shí)戰(zhàn)

image

該gif圖來源于UI志

可以看到這張gif圖item跳轉(zhuǎn)用到共享元素動(dòng)畫,在第二個(gè)詳情頁面共享動(dòng)畫進(jìn)入后開啟頁面內(nèi)其他元素透明+放大 動(dòng)畫。關(guān)閉詳情頁面依次反向執(zhí)行開啟的流程:詳情頁面描述和標(biāo)題還有圖片左上角的關(guān)閉按鈕依次執(zhí)行透明+縮放動(dòng)畫,等這些操作執(zhí)行完畢,執(zhí)行finishAfterTransition,共享元素自會(huì)回到之前列表中開啟的位置。

最后看一下實(shí)現(xiàn)的效果圖

實(shí)現(xiàn)代碼就不貼了,點(diǎn)我查看代碼

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

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

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