MaterialDesign系列文章(三)過渡動畫的實現

筆墨Android發表於2018-05-03

大家好!wo又來了。。。幾天不見甚是想念!!!最近由於比較忙,請原諒我的懶惰!!!寫關於MaterialDesign的文章已經有好幾篇了。昨天無意間看見了一個關於過度動畫的概念,所以今天想和大家分享一下關於過度動畫的實戰!這裡嘮叨一下,網上很多文章都是針對於頁面跳轉的動畫,其實過渡動畫不光可以實現頁面跳轉,對一些控制元件也是可以生效的。

如果你想直接案例解析的話,可以直接從第三條開始看!!!

先簡單看一下效果:

過度動畫的效果圖

本文知識點:

  • 過渡動畫的介紹:
  • 過渡動畫類的介紹:
  • 過渡動畫的按鈕分析:

過度動畫的介紹

關於過度動畫,是在Android5.0中提出的。它通過運動和切換不同狀態之間的元素來產生各種動畫效果。開始的時候,沒有對低版本進行適配,github上有位大神進行了相應的適類似配(使用方式) 地址如下,後來在support包中新增了對低版本的適配,所以使用的時候要注意,否則會出現警告的。。。

大體的繼承關係如下:

  • TransitionManager
  • Transition
    • Visibility
      • Explode
      • Slide
      • Fade
    • ChangeBounds
    • TransitionSet
    • TextScale
    • ChangeClipBounds
    • ChangeImageTransform
    • ChangeScroll
    • ChangeTransform

過度動畫類的介紹:

1.TransitionManager 過度動畫管理類

這個動畫管理類,主要協調相應的動畫管理。重要的API有以下幾個:

  • TransitionManager.beginDelayedTransition(@NonNull final ViewGroup sceneRoot, @Nullable Transition transition) 對一個根檢視過渡的方法
  • TransitionManager.go(@NonNull Scene scene, @Nullable Transition transition) 對一個檢視圖層進行過渡的方法
  • TransitionManager.endTransitions(final ViewGroup sceneRoot) 結束所有根佈局的過渡

1.1 TransitionManager.beginDelayedTransition()

這個主要是針對根試圖的過渡方法:這裡主要是處理一個頁面中的過渡! 這個方法還有一個過載的方法:

beginDelayedTransition(@NonNull final ViewGroup sceneRoot)
beginDelayedTransition(@NonNull final ViewGroup sceneRoot, @Nullable Transition transition)
複製程式碼
  • 引數1:根佈局
  • 引數2:相應的過渡動畫型別

這裡的動畫過渡型別有以下幾類:

  • AutoTransition 預設的樣式
  • Fade 淡入淡出
  • Explode 炸裂效果
  • Slide 移動

具體程式碼如下:

        mCl_root = findViewById(R.id.cl_root);
        mTvText = findViewById(R.id.tv_text);
//        Slide slide = new Slide(Gravity.BOTTOM);
//        Explode explode = new Explode(); 
        Fade fade = new Fade();
        TransitionManager.beginDelayedTransition(mCl_root, fade);

        if (mTvText.getVisibility() == View.VISIBLE) {
            mTvText.setVisibility(View.GONE);
        } else {
            mTvText.setVisibility(View.VISIBLE);
        }
複製程式碼

上面會有三種效果,這裡建議大家自己設定以下看看效果!

如果你想設定動畫的一些其他屬性的話,可以這樣:

    fade.setDuration(300);//設定動畫時長
    fade.setInterpolator(new FastOutSlowInInterpolator());//設定插值器
    fade.setStartDelay(200);//設定延時
複製程式碼

上面所有都有這些屬性的!!!

這裡注意一點,如果你想要使用縮放、顏色、旋轉變化的話,那個適配的庫中有!其實就是繼承了Transition這個類,自己去實現罷了!感興趣的童鞋可以去試試。。。

1.2 TransitionManager.go()

說實話,這個東西我研究了好久,各種百度才理解了怎麼用,原諒我的愚笨。。。先說說我對這個東西的理解啊!從一個佈局檔案過渡到另一個佈局檔案(注意,這裡是佈局檔案的過渡)。我不知道怎麼用語言去形容。。。

MaterialDesign系列文章(三)過渡動畫的實現

我們們看圖吧!

展示效果

上面的圖大家都看懂了吧!其實就是兩個佈局檔案的過渡,中間所有的過渡都是TransitionManager幫我們封裝完成的,你可以選擇相應的樣式進行顯示!我們看看具體的程式碼實現吧!

首先要給大家說個類,這個類在此過度動畫中起著至關重要的作用,這個類就是Scene,註釋裡解釋為一個檢視層次,也就相當於上面提到的佈局檔案過渡中的佈局,其實這個東西你理解成佈局也是可以的!

Scene getSceneForLayout(@NonNull ViewGroup sceneRoot, @LayoutRes int layoutId, @NonNull Context context)
複製程式碼
  • 引數1:根佈局(操作的檢視層次所在的佈局的跟標籤)
  • 引數2:檢視層級佈局的索引
  • 引數3:根佈局

這樣就可以建立一個相應的檢視層級了!下面我們來說程式碼:

因為是兩個佈局檔案的過渡,所以這裡我們需要一個總佈局,兩個供切換的佈局,這裡你可能不太明白,一會就明白了!!!相信我,不行你向我丟BUG;

MaterialDesign系列文章(三)過渡動畫的實現

  • 總佈局(根佈局)
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:id="@+id/cl_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.jinlong.newmaterialdesign.animation.TransitionManagerActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:title="展示動畫效果"
        app:titleTextColor="@android:color/white" />

    <Button
        android:id="@+id/btn_animation"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="animation"
        android:text="實現動畫變換"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="scene"
        android:text="變換動畫"
        app:layout_constraintLeft_toRightOf="@id/btn_animation"
        app:layout_constraintTop_toBottomOf="@id/toolbar" />

    <TextView
        android:id="@+id/tv_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="20dp"
        android:text="請注意這些文字的變化"
        app:layout_constraintTop_toBottomOf="@id/btn_animation" />

   <FrameLayout
       android:id="@+id/rl_root"
       android:layout_width="match_parent"
       app:layout_constraintTop_toBottomOf="@id/tv_text"
       android:layout_height="200dp">

       <include layout="@layout/scene1" />
   </FrameLayout>
</android.support.constraint.ConstraintLayout>
複製程式碼

這裡主要看FrameLayout中包裹的那個佈局,這是一個佈局檔案!都是基於這裡來的!!!

  • 佈局1
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_1"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:src="@mipmap/heard_1" />

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_2"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/heard_2" />

</RelativeLayout>
複製程式碼
  • 佈局2
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="200dp">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_2"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="20dp"
        android:src="@mipmap/heard_2" />

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/civ_1"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="20dp"
        android:layout_marginRight="20dp"
        android:src="@mipmap/heard_1" />

</RelativeLayout>
複製程式碼

MaterialDesign系列文章(三)過渡動畫的實現
MaterialDesign系列文章(三)過渡動畫的實現

兩個佈局的展現形式是這樣的。仔細的小夥伴可能看到了,兩個佈局的展示形式是一樣的,只是兩個控制元件的位置互換了一下。其實動畫的表現形式就是那個男孩的頭像從左上角移動到右下角,女孩的頭像從右下角移動到左上角,正好的兩個佈局的展現形式。所以講到這裡我像你就會明白為什麼我一直強調是佈局檔案的移動了吧!

  • 程式碼實現 初始化檢視圖層
    FrameLayout layout = findViewById(R.id.rl_root);
    mScene1 = Scene.getSceneForLayout(layout, R.layout.scene1, this);
    mScene2 = Scene.getSceneForLayout(layout, R.layout.scene2, this);
    TransitionManager.go(mScene1);
複製程式碼

這裡建立了兩個檢視層次,開始的時候,直接預設顯示到檢視圖層1,點選按鈕進行如下操作:

    TransitionManager.go(isScene2 ? mScene1 : mScene2,new ChangeBounds());
    isScene2 = !isScene2;
複製程式碼

這裡通過一個變數進行判斷,如果顯示的是檢視圖層2就切換到圖層1,否則相反!就能實現上面的效果了!!!整體程式碼就不貼了,自己動手試一下,這樣記得牢靠!其實不只侷限於這種表現形式。如果三個的話,你建立三個檢視圖層也是可以切換的。這個你就要發揮想象空間了!

2.TransitionSet類介紹

關於這個類,可以簡單理解為動畫集合,可以合併多個動畫一起顯示出來。

    TransitionSet transitionSet = new TransitionSet();
    transitionSet.addTransition(slide);
    transitionSet.addTransition(fade);
複製程式碼

這樣就能直接把上面的過渡疊加起來了!

2.1 TransitionSet的XML使用

這裡我覺得有必要說明以下xml的使用,首先要明確,這個佈局的放置位置res/transition/xxx.xml。這裡先貼一個整體的xml我在逐一講解:

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential"><!--是否按照順序執行-->

    <fade android:duration="500">
        <targets>
            <target android:targetId="@id/toolBar"/>
            <target android:targetId="@android:id/statusBarBackground"/>
            <target android:targetId="@id/tv_show"/>
        </targets>
    </fade>
    
    <slide android:startDelay="600">
        <targets>
            <target android:targetId="@id/icon_sf"/>
        </targets>
    </slide>
    
    <slide android:startDelay="700">
        <targets>
            <target android:targetId="@id/icon_bh"/>
        </targets>
    </slide>

    <slide
        android:slideEdge="left"
        android:startDelay="500">
        <targets>
            <target android:targetId="@id/tv_show"/>
        </targets>
    </slide>
</transitionSet>
複製程式碼
  • android:transitionOrdering 代表執行順序
    • sequential 順序執行(寫在上面的先執行)
    • together 一起執行
  • fade標籤 漸變的屬性
  • slide標籤 移動的屬性
  • changeBounds標籤
    • targets標籤 這個裡面主要是維護相應的那個控制元件需要過渡
      • android:excludeId 除去那個ID的控制元件不進行過渡(一般用於狀態列,@android:id/statusBarBackground狀態列的ID)
  • android:startDelay 延時的時間

基本上能用到的屬性就這麼多!!!其實這個清單檔案裡面主要維護的是哪個控制元件需要那種過渡。就醬紫!!!

MaterialDesign系列文章(三)過渡動畫的實現

講了這麼多你是不是還是一臉懵逼,其實我也是,不知道怎麼去表達,所以我準備用例項去講解,便於自己的記憶和你的理解!!!

3.過渡動畫的案例分析:

終於到了本文的重點了,其實我開始學這個東西的時候也是一臉懵逼,不太理解,但是後來看到程式碼的時候,就不那麼費勁了。。。。先從首頁頂部那個圖片開始說起吧!

3.1 頂部過渡動畫的實現

關於上面的動畫效果,主要記住共享這個概念,為什麼這麼說呢?因為上面這個動畫是根據共享去關聯的。所謂的依靠共享,主要是這個View過渡到下一個View的相應展現形式!那麼就存在一個問題了,共享你必須兩個View有共同的一個View,然後存在共同的ID。否則下個頁面怎麼知道你想要拿什麼和我共享呢?這裡暫時定一下Activity頁面A(後面用A代替)和Activity頁面B(後面用B代替)。

  • A頁面的佈局
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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:id="@+id/cl_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.jinlong.newmaterialdesign.animation.TransitionManagerActivity">

    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:title="展示動畫效果"
        app:titleTextColor="@android:color/white" />

    <LinearLayout
        android:id="@+id/ll_shared"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:onClick="sharedAnimation"
        android:orientation="horizontal"
        app:layout_constraintTop_toBottomOf="@id/toolbar">

        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/civ_heard"
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:src="@mipmap/heard_1" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:layout_marginLeft="30dp"
            android:text="這是一個標題欄目" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>
複製程式碼

這裡注意上面的CircleImageView控制元件的ID和TextView控制元件的ID,上面說了,後面會用到!

  • B頁面的佈局是這樣的
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
    tools:context="com.jinlong.newmaterialdesign.animation.SharedActivity">


    <android.support.v7.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimaryDark"
        app:title="展示動畫效果"
        app:titleTextColor="@android:color/white" />


    <LinearLayout
        android:id="@+id/fl_top"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintTop_toBottomOf="@id/toolbar">

        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/civ_heard"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/heard_1"
            android:transitionName="shared_image" />

        <TextView
            android:id="@+id/tv_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="這也是相應的標題"
            android:transitionName="shared_textview" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>
複製程式碼

這裡的ID和上面的ID是一樣的。並且注意這裡面設定的transitionName標籤,這個標籤是設定共享內容的,馬上你就會知道為什麼了。。。

  • 通過程式碼進行相應的跳轉
        CircleImageView civHead = findViewById(R.id.civ_heard);
        TextView tvTitle = findViewById(R.id.tv_title);

        Intent intent = new Intent(this, SharedActivity.class);
        ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(this,
                new Pair<View, String>(civHead, "shared_image"),
                new Pair<View, String>(tvTitle, "shared_textview"));
        startActivity(intent, optionsCompat.toBundle());
複製程式碼

3.1.1方法說明:

  • ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity, Pair<View, String>... sharedElements)
  • ActivityOptionsCompat makeSceneTransitionAnimation(Activity activity, View sharedElement, String sharedElementName) 這個個方法是實現共享元素平移到第二個頁面的主要方法,兩個方法主要區別就是上面的可以共享多個View,下面的只能共享一個View (注意這個只有在21以上的版本才有效)
  • ActivityOptionsCompat makeCustomAnimation(Context context, int enterResId, int exitResId) 這個類似於之前的overridePendingTransition的效果,傳入的引數也都基本上一樣
  • ActivityOptionsCompat makeScaleUpAnimation(View source, int startX, int startY, int startWidth, int startHeight) 從頁面的具體那個矩形,開始放大到相應的螢幕位置,這裡面的引數,基本上都是和相應的矩形有關!
  • ActivityOptionsCompat makeThumbnailScaleUpAnimation(View source, Bitmap thumbnail, int startX, int startY) 縮圖從指定的位置擴充套件到全屏。用於圖片的顯示應該挺好的!

看了上面的內容,我相信你能寫出下面的動畫效果了!

虛擬機器效果錄得不怎麼好

3.2 後面那個擴散圓形的效果

其實後面那個圓形擴散的效果是在5.0以後才有的效果(使用的是ViewAnimationUtils),所以要使用的話,必須在5.0機器上測試。否則是沒有效果的!!!程式碼是這樣的...

                    Animator circularReveal = ViewAnimationUtils.createCircularReveal(mImage_bg, 0, mImage_bg.getHeight(), 0, Math.max(mImage_bg.getHeight(), mImage_bg.getWidth()));
                    mImage_bg.setBackgroundColor(Color.BLACK);
                    circularReveal.setDuration(600);
                    circularReveal.start(); 
複製程式碼

其實用到的只有一個API方法Animator createCircularReveal(View view, int centerX, int centerY, float startRadius, float endRadius) 這裡面的引數基本上就是中心點座標,擴散半徑的確定,這裡就不做太多解釋了!這個你可以在在21的版本試一下就可以了!

3.3 進入動畫的設定

在B頁面其實也是可以設定相應的入場動畫的

  • getWindow().setEnterTransition(Transition transition); 設定入場時候的動畫
  • getWindow().setReturnTransition(Transition transition); 設定返回時候的動畫
  • getWindow().setSharedElementEnterTransition(Transition transition); 設定進入時存在共享的動畫
  • getWindow().setSharedElementReturnTransition(Transition transition) 設定返回時存在共享的動畫

穿插一個問題,就是Transition怎麼從xml中引入

TransitionInflater.from(this).inflateTransition(R.transition.return_slide)
複製程式碼

這裡的Transition可以是一個具體的子類,也可以是一個清單檔案。剩下的就是發揮大家的想象空間了!把上面的頁面剩下的程式碼貼一下,就結束今天的內容吧!感覺篇幅有點長,感興趣的同學,可以看看我GitHub裡面的專案!!!


這篇文章我寫了好幾天,不知道該怎麼去寫,感覺寫的有點亂,希望大家不要介意,有什麼不懂的留言給我,我一定及時回覆......今天就醬紫了!

相關文章