Android動畫實現詳解

Code4Android發表於2017-05-21

原始碼傳送門

前言

我們都知道,漂亮的使用者介面是衡量一款應用"好壞"很重要的依據,因為人都是視覺動物,就好比說花容月貌總有男人為之傾倒,英俊瀟灑總能博得芳心。這是一個不容置疑的事實,那麼我們的應用也是如此,一個漂亮的使用者互動介面能提升使用者對應用的好感,提升使用者體驗。而動畫是提升使用者體驗的一個重要因素,好的動畫互動讓人用著更舒心,那麼今天的這篇文章就是介紹Android中動畫實現,讓我們的應用動起來。

Android動畫分類

在Android中我們一般將動畫分為兩類,一類是View Animation(檢視動畫),一類是Property Animation,當然也有說分為三種,Frame Animation,Tween Animation,和Property Animation,由於前兩種一般都是作用於整個View的,所以就統稱為View Animation。在谷歌官方文件中也是按兩種分類來講解的。

在Android 5.0開始增加了Material Design ,Material Design 中實現了一些動畫為使用者提供操作反饋並在使用者與您的應用進行互動時提供視覺連續性。 它將為按鈕與操作行為轉換提供一些預設動畫,我們可以定製觸控反饋,使用揭露效果,定製操作行為轉換,指定定製轉換,使用轉換啟動一個操作行為,以共享元素啟動一個操作行為等等。

Frame Animation

由於Frame Animation,Tween Animation的實現還是有區別的,暫且就將這兩種方式實現分開介紹,Frame Animation其實就是將一系列的圖片一張一張的展示出來,這個和我們看電影是類似的,由於人眼有一個可接收的暫留時間,這也就是為什麼人線上看視訊會感覺卡頓。當然我們實現Frame Animation就是這個依據,當播放相同的圖片張數用時越短也就越流暢,自然人就會感覺是一個動畫。我們常見的Gif其實也是幀動畫,如果你用PhotoShop開啟Gif動畫,會發現裡面是有很多圖層的也就是很多幀。

對於Frame動畫有兩種實現方式,第一種方式就是在drawable資料夾下建立一個xml檔案。它的語法很簡單,如下

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot=["true" | "false"] >
    <item
        android:drawable="@[package:]drawable/drawable_resource_name"
        android:duration="integer" />
</animation-list>複製程式碼

看了上面你會發現實現Frame動畫很簡單,屬性很少,animation-list 是動畫的根元素,在根元素中的oneshot屬性表示動畫執行次數,如果設定為true表示只播放一次,如果false則表示會一直迴圈執行。在根元素下有item元素,該元素就是我們要新增的圖片,每一個item表示一幀,item下的drawable就是我們的圖片資源,duration就是該幀動畫執行的時間。例如

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@mipmap/run1"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run2"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run3"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run4"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run5"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run6"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run7"
        android:duration="200" />
    <item
        android:drawable="@mipmap/run8"
        android:duration="200" />
</animation-list>複製程式碼

使用方法如下

//可以在xml中設定background   
     imageView.setBackgroundResource(R.drawable.frame_run_animation);
AnimationDrawable animationDrawable = (AnimationDrawable) imageView.getBackground();
animationDrawable.start();複製程式碼

Android動畫實現詳解
frame.gif

執行效果圖如上,在上面我們沒有新增oneshot屬性,則該屬性預設false,也就是說該動畫會一直迴圈執行,當我們設定true後則播放到最後一幀時動畫停止,當我們想停止時可以使用AnimationDrawable 的stop方法,它還提供了isRunning()方法判斷是否已經在執行動畫。另外我們還需要注意的是小心OOM。

當然用程式碼實現也很簡單,如下

    private void addFrame() {
        AnimationDrawable animationDrawable = new AnimationDrawable();
        int[] mipmaps = new int[]{R.mipmap.run1, R.mipmap.run2, R.mipmap.run3,
                R.mipmap.run4, R.mipmap.run5, R.mipmap.run6, R.mipmap.run7, R.mipmap.run8,};
        for (int i = 1; i <= 8; i++) {
            int id=mipmaps[i-1];
            //或者使用下面方式,注意如果圖片資源放在drawable下時將mipmap修改下
            //int id = getResources().getIdentifier("run" + i, "mipmap", getPackageName());
            Drawable drawable = getResources().getDrawable(id);
            animationDrawable.addFrame(drawable, 200);
        }
        animationDrawable.setOneShot(false);
        imageView.setBackgroundDrawable(animationDrawable);
        animationDrawable.start();
    }複製程式碼

Tween Animation

Tween Animation即補間動畫,主要分為四種,分別是平移、縮放、旋轉、透明度,直接上語法

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    //插值器
    android:interpolator="@[package:]anim/interpolator_resource"
    //動畫結束後View是否停留在結束的位置
    android:fillAfter=["true" | "false"] 
    //重複的模式,預設為restart,即重頭開始重新執行,reverse即從結束開始向前重新執行
   android:repeatMode="restart/reverse"
    //子元素是否共用此插值器
    android:shareInterpolator=["true" | "false"] >
    <alpha
        //開始透明度0.0(全透明)到1.0(完全不透明)
        android:fromAlpha="float"
        android:toAlpha="float"
        //動畫執行時間
        android:duration="integer" />
    <scale 
        //其實X縮放大於0,1的時候表示不縮放,小於1縮小,大於1放大
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
       //縮放中心,也可以穿fraction值
        android:pivotX="float"
        android:pivotY="float"
        android:duration="integer" />
    <translate
        //表示 x 的起始值
        android:fromXDelta="float/fraction"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" 
        android:duration="integer" />
    <rotate
        //起始的旋轉角度
        android:fromDegrees="float"
        android:toDegrees="float"
        android:pivotX="float"
        android:pivotY="float"
        android:duration="integer"  />
    <set>
        ...
    </set>
</set>複製程式碼

這是官方給的語法,set 是一個動畫集合,內部可以是多個動畫的組合,也可以巢狀set,這裡包含了動畫實現的所有屬性。在上面的語法中我們需要注意的是平移的時候其實位置接受百分比數值:從-100到100的值,以“%”結尾,表示百分比相對於自身;從-100到100的值,以“%p”結尾,表示百分比相對於父容器。例如平移開始位置在自身中間則是50%,如平時開始位置在父容器的則是50%p.

例如有些人給我們的Activity會加一些從左邊進右邊出的動畫,那麼當我們開啟Activity時將Activity佈局的fromXDelta值-100%p並將toXDelta為0%p,那麼我們看到的效果就是從左邊進入了。

插值器

在動畫插值器起的作用主要是改變動畫的執行速率,一般情況我們不需要自己實現插值器,因為在Android中已經給我們提供了9種插值器,應該夠我們使用了,我們使用插值器後會讓動畫執行的效果更酷炫,當然想自定義插值器也不難,可以檢視已經實現插值器原始碼做參考。

  • accelerate_decelerate_interpolator:先加速後減速
  • accelerate_interpolator:一直加速
  • anticipate_interpolator: 開始的時候先向後甩一點然後向前,就好比人扔東西會先向後甩一下,這樣才能拋的遠
  • anticipate_overshoot_interpolator:和上一種插值器相似,先向後拋然後向前,到達終點後會有回彈一下效果,好比我們將球拋到牆上,然後反彈回來
  • bounce_interpolator:動畫結束的時候彈起,類似皮球落地,會彈幾下才停止
  • cycle_interpolator:動畫迴圈播放特定的次數回到原點,速率改變沿著正弦曲線
  • decelerate_interpolator:減速的插值器,剛開始速度快,然後越來越慢直到停止
  • linear_interpolator:線性的插值器。從開始到結束勻速運動
  • overshoot_interpolator:向前超過設定值一點然後返回

下面簡單實現一個動畫,動畫效果如下面截圖,是一個透明度,平移,縮放的動畫同時執行的動畫。

Android動畫實現詳解
frame1.gif

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/bounce_interpolator"
    android:shareInterpolator="true">
    <translate
        android:duration="400"
        android:fromXDelta="50%p"
        android:fromYDelta="-50%p"
        android:toXDelta="0%"
        android:toYDelta="0%" />
    <scale
        android:duration="400"
        android:fromXScale="0.0"
        android:fromYScale="0.0"
        android:toXScale="1.0"
        android:toYScale="1.0" />
    <alpha
        android:duration="400"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
</set>複製程式碼

然後使用下面程式碼給ImageView加入動畫。

 Animation animation= AnimationUtils.loadAnimation(this,R.anim.anim_in_left_top);
  imageView.startAnimation(animation);複製程式碼

當然我們也可給動畫加上監聽。如

animation.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                    }
                    @Override
                    public void onAnimationEnd(Animation animation) {
                    }
                    @Override
                    public void onAnimationRepeat(Animation animation) {
                    }
                });複製程式碼

上面的監聽分別是動畫開始結束和更新時候的回撥。我們在回撥中做一些額外的操作。在程式碼中實現動畫就不在細說,主要對應AnimationSetTranslateAnimationScaleAnimationAlphaAnimationRotateAnimation

Propterty Animation

屬性動畫是3.0之後引入的,在View動畫中雖然我們看到了我們的控制元件位置發生發生變化,比如Button雖然位置變化了,但是點選響應區域還在原來的位置。而屬性動畫就可以解決這種問題。它可以作用於View的屬性。
語法

<set
//執行的順序together同時執行,sequentially連續執行
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
      //屬性動畫名字,例如alpha" 或者"backgroundColor"等
        android:propertyName="string"
        android:duration="int"
        //屬性的開始值
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        //該動畫開始的延遲時間
        android:startOffset="int"
        //動畫重複次數,-1表示一直迴圈,1表示迴圈一次也就是播放兩次,預設0,播放一次
        android:repeatCount="int"
        //repeatCount設定-1時才會有效果
        android:repeatMode=["repeat" | "reverse"]
        //如果值是顏色,則不用使用此屬性
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>複製程式碼

下面列出了常見的屬性名字,另外需要注意的是,使用屬性動畫時,必須有相應屬性的set/get方法,否則屬性動畫沒有效果的。

  • translationX 和 translationY : 控制View距離左邊和頂部的距離的增加值。是一個相對值。相對於自身位置的具體。
  • rotation 、 rotationX 和 rotationY : rotation 是控制View圍繞其支點進行旋轉。 rotationX 和 rotationY 分別是圍繞X軸和Y軸旋轉。

  • scaleX 和 scaleY : 控制View的縮放。

  • pivotX 和 pivotY : 控制View的支點位置,進行旋轉和縮放,預設是View的中點。它們都是 float 值, 0 表示View的最左邊和最頂端, 1 表示最右端和最下端。

  • alpha : 控制View的透明度。

  • x 和 y : 控制View在佈局容器中距離左邊和頂部的距離。是一個絕對值。

例如我們實現一個旋轉加透明度變化的動畫,效果圖如下

Android動畫實現詳解
frame1.gif

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">
    <objectAnimator
        android:duration="500"
        android:propertyName="rotation"
        android:valueFrom="0"
        android:valueTo="180" />
    <objectAnimator
        android:duration="500"
        android:propertyName="alpha"
        android:valueFrom="1.0f"
        android:valueTo="0.5f" />
</set>複製程式碼

然後

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this,
                R.animator.property_animator);
set.setTarget(imageView);
set.start();複製程式碼

當然不用xml書寫也是很簡單的,如下程式碼

      ObjectAnimator.ofFloat(imageView,"rotation",0,180,90,180)
                .setDuration(2000).start();複製程式碼

程式碼實現的效果就是在2秒內先旋轉到180度,在回到90度在轉回180度
效果圖如

Android動畫實現詳解
frame1.gif

在上面程式碼實現了一直屬性動畫,那麼如果我們想同時作用幾個屬性那該如何操作呢。此時我們有兩種實現方式分別是類PropertyValuesHolder和AnimatorSet,話不多說,先上圖再直接上程式碼。

Android動畫實現詳解
frame1.gif

    private void startPropertyAnimation3(){
        PropertyValuesHolder translationX = PropertyValuesHolder.ofFloat("translationX", -200-100,100, 200,300);
        PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 2.0f);
        PropertyValuesHolder rotate = PropertyValuesHolder.ofFloat("rotation", 0f, 360f);
        PropertyValuesHolder rotationY = PropertyValuesHolder.ofFloat("rotationX", 0f, 180f);
        ObjectAnimator together = ObjectAnimator.ofPropertyValuesHolder(imageView, translationX,rotate, scaleX, rotationY);
        together.setDuration(3000);
        together.start();
    }
//或者使用AnimatorSet,此方法使用的是按順序播放。
    private void startPropertyAnimation4(){
        ObjectAnimator translationX = ObjectAnimator.ofFloat(imageView, "translationX", -200,-100,100, 200,300);
        ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 1.0f, 2.0f).setDuration(1000);
        ObjectAnimator rotation = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f).setDuration(1000);
        ObjectAnimator rotationX=ObjectAnimator.ofFloat(imageView,"rotationX", 0f, 180f).setDuration(1000);
        AnimatorSet set = new AnimatorSet();
        set.playSequentially(translationX, scaleX, rotation,rotationX);
        set.setDuration(4000);
        set.start();
    }複製程式碼

Fragment/Activity動畫

其實實現Activity及Fragment切換動畫也是很簡單的,具體的動畫效果製作可以使用即使上面介紹的補間動畫。例如我們Fragment動畫。

FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
transaction.replace(R.id.fragment_container, fragment, fragmentTag);
transaction.commit();複製程式碼

Activity實現動畫也很簡單,在Activity中提供了overridePendingTransition(int enterAnim, int exitAnim) 方法,該方法接收兩個引數,第一個引數是Activity進入時的動畫,第二個引數是Activity退出時的動畫。該方法一般寫在startActivity()後和finish()後,如果我們想開啟或者退出不顯示動畫,可將引數設定為0。

在上面的我們介紹了Activity/Fragment在程式碼中實現動畫的方法,當然還有一種簡單的實現方式,那就是在主題中設定動畫。
對於Activity

    <style name="CustomeActivityStyle" parent="AppTheme">
        <item name="android:windowAnimationStyle">@style/AnimationStyle</item>
    </style>
    <style name="AnimationStyle">
        <item name="android:activityCloseEnterAnimation">@anim/slide_in_left</item>
        <item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
        <item name="android:activityOpenEnterAnimation">@anim/slide_in_left</item>
        <item name="android:activityOpenExitAnimation">@anim/slide_out_right</item>
    </style>複製程式碼

可能你不太理解為什麼是設定了四種,假如有Activity1和Activity2。當我們在Activity1中跳轉到Activity2時,Activity1在頁面上消失是執行的:activityOpenExitAnimation動畫,Activity2出現在螢幕上執行的動畫是activityOpenEnterAnimation。當Activity2 finish返回Activity1時,Activity2執行的動畫是activityCloseExitAnimation,Activity1顯示在螢幕執行的是activityCloseEnterAnimation。建立上面主題後我們需要將該主題應用到我們的Activty中就可以了。

同理Fragment也可相應設定,如activityCloseEnterAnimation改為fragmentCloseEnterAnimation即可。
除此之外我們也可以使用windowAnimation,它包括 windowEnterAnimation 和 windowExitAnimation。注意的一點就是WindowAnimation的控制權大於Activity/Fragment Animation的控制權。

除了上面介紹的動畫實現,還有一些動畫是從Android5.0增加的,你可以參考文末給出的連結文章,酷炫的Activity切換動畫,打造更好的使用者體驗。個人感覺這篇文章介紹的挺詳細。對本文缺少的內容也做了一個補充。到這裡該篇文章就暫時告一段落,有問題歡迎指出,Have a wonderful day.

定義定製動畫
酷炫的Activity切換動畫,打造更好的使用者體驗

相關文章