【總結(jié)】安卓動畫

本篇為對android動畫機(jī)制的粗略總結(jié),目的是方便查詢而非講解,所以代碼部分的注釋比較多,但是很多該配圖的地方?jīng)]有配。
安卓的動畫是一種比較基本的特效,項目中經(jīng)常會用到
安卓的動畫分為三種,分別是View動畫,幀動畫,屬性動畫。

View動畫

View動畫的重點是動畫,是視圖(View)層面上的一種變換,操作的重點在視圖上,這與屬性動畫是完全不同的。
View動畫,也有叫補(bǔ)間動畫的,就是Flash中的那個補(bǔ)間動畫,給定初始值跟結(jié)束值,自動計算出過程的一種動畫形式。
我個人比較喜歡的用法是用XML文件寫動畫,然后用代碼加載使用,這樣的動畫可讀性非常好。
view動畫的所屬文件夾為res/anim文件夾

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:interpolator="@android:anim/cycle_interpolator"
    android:shareInterpolator="false">

    <!--
    duration 代表動畫執(zhí)行時間
    若<set>與其子項目都設(shè)置有 duration 屬性,則以<set duration>為準(zhǔn),其子項目屬性會被覆蓋
    如下<alpha android:duration="5000" >會被覆蓋

    interpolator 代表動畫插值器
    shareInterpolator 代表整個set是否共享插值器,默認(rèn)為true

    若<set>與其子項目都設(shè)置有 interpolator 屬性,且shareInterpolator 設(shè)置為true,則以<set interpolator >為準(zhǔn),其子項目屬性會被覆蓋
    若shareInterpolator 設(shè)置為false,則以其子項目屬性為準(zhǔn)
    如下<alpha android:interpolator="@android:anim/linear_interpolator">會生效
    但其余子項目沒有設(shè)置插值器,所以默認(rèn)為<set android:interpolator="@android:anim/cycle_interpolator">
    -->

    <alpha
        android:duration="5000"
        android:fromAlpha="1"
        android:interpolator="@android:anim/linear_interpolator"
        android:toAlpha="0" />

    <!--
    alpha 透明度變換,范圍為[0,1],沒有單位
    fromAlpha 透明度變換的起始值
    toAlpha 透明變換的結(jié)束值
    -->

    <translate
        android:fromXDelta="0%"
        android:fromYDelta="0%"
        android:toXDelta="100%"
        android:toYDelta="100%" />

    <!--
    translate 平移變換,可填入浮點型數(shù)字,無上限。或者填入百分?jǐn)?shù)。我比較傾向于填寫百分?jǐn)?shù),比較直觀。
    fromXDelta,fromXDelta 平移變換的起始位置,0%為自身的位置
    toXDelta,toYDelta 平移變換的結(jié)束位置,100%為當(dāng)前View的寬高
    -->

    <rotate
        android:fromDegrees="0"
        android:pivotX="10"
        android:pivotY="10"
        android:toDegrees="180" />

    <!--
    rotate 旋轉(zhuǎn)變換,一般填入整型,代表旋轉(zhuǎn)角度
    fromDegrees 旋轉(zhuǎn)變換起始角度
    toDegrees 旋轉(zhuǎn)變換結(jié)束角度
    pivotX 軸點,代表變換的中心位置,此時為旋轉(zhuǎn)圓心的X值
    pivotY 軸點,代表變換的中心位置,此時為旋轉(zhuǎn)圓心的Y值
    -->

    <scale
        android:fromXScale="0%"
        android:fromYScale="0%"
        android:pivotX="10"
        android:pivotY="10"
        android:toXScale="100%"
        android:toYScale="100%" />

    <!--
    scale 縮放變換,一般填入整型,單位使用dp之類的。或者填入百分?jǐn)?shù)。我比較傾向于填寫百分?jǐn)?shù),比較直觀。
    fromXScale,fromYScale 縮放變換的起始值
    toXScale,toYScale 縮放變換的結(jié)束值
    pivotX 軸點,代表變換的中心位置,此時為縮放中心的X值
    pivotY 軸點,代表變換的中心位置,此時為縮放中心的Y值
    -->

    <set android:duration="4000">
        <!--
         外層set設(shè)置的屬性,內(nèi)層set都可以設(shè)置,重復(fù)時,覆蓋規(guī)則與其他子項目相同
         -->
    </set>
</set>

使用的時候加載動畫并且執(zhí)行就好,非常簡單。

Animation anim = AnimationUtils.loadAnimation(this, R.anim.anim_view_anim);
View.startAnimation(anim);

【此處假裝有動圖】

有時候有的屬性沒有自動提示,敲出不報錯就沒問題,
例如android:interpolator="@android:anim/cycle_interpolator"有時候編譯器不會提示
插值器,估值器的概念,下文會說。

實現(xiàn)一個動畫,除了使用xml意外,還可以用代碼來寫,當(dāng)動畫的某些參數(shù)需要動態(tài)調(diào)整的時候需要這么使用。但寫起來比較繁瑣,看起來比較亂,所以就只放一個例子,使用方法相同。

    public void translate(View v) {
        // 創(chuàng)建位移補(bǔ)間動畫
        // ta = new TranslateAnimation(-100, 100, -50, 50);
        ta = new TranslateAnimation(Animation.RELATIVE_TO_SELF, -2,
                Animation.RELATIVE_TO_SELF, 3, Animation.RELATIVE_TO_SELF, 0,
                Animation.RELATIVE_TO_SELF, 0);
        // 指定動畫持續(xù)時間
        ta.setDuration(2000);
        // 設(shè)置重復(fù)播放的次數(shù)
        ta.setRepeatCount(1);
        // 設(shè)置重復(fù)播放的模式
        ta.setRepeatMode(Animation.REVERSE);
        // 填充動畫在結(jié)束的位置上
        ta.setFillAfter(true);
        // 播放動畫
        iv.startAnimation(ta);
    }

View動畫也可以自定義,通過繼承Animation就可以實現(xiàn),只不過由于動畫實質(zhì)上是一種數(shù)學(xué)變換過程,實現(xiàn)起來比較麻煩。項目中系統(tǒng)自帶的幾種動畫組合就能解決大部分問題,所以自定義View動畫不常用。

View動畫的其他用法

View動畫還可以用于設(shè)定子元素的出場效果,以及Activity的切換效果。

LayoutAnimation

LayoutAnimation作用于ViewGroup,為其制定一個動畫,當(dāng)它的子元素初始化時,會帶有這個效果,常用在RecycleView上。

首先,我們需要一個layoutAnimation

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/rv_layout_anim_item"
    android:animationOrder="normal"
    android:delay="10%" />
    <!--
       android:animation 為子元素指定入場動畫
       android:animationOrder 表示動畫的順序,normal表示順序顯示,reverse表示逆向顯示,后面的先入場,random表示隨機(jī)
       android:delay 表示子元素相對于上一個入場的元素的延遲時間,比如一個動畫100ms,50%的延遲則表明下一個元素延遲50ms
    -->

然后我們需要一個視圖動畫

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:interpolator="@android:anim/decelerate_interpolator">
    <translate
        android:fromYDelta="-20%"
        android:toYDelta="0" />

    <alpha
        android:fromAlpha="0"
        android:toAlpha="1" />
</set>

最后我們?yōu)镽ecycleView指定這個入場動畫

        <android.support.v7.widget.RecyclerView
            ...
            android:layoutAnimation="@anim/rv_layout_anim" />

入場動畫也可以為其他ViewGroup指定,效果略有差異。

Activity的切換效果
overridePendingTransition(R.anim.anim1, R.anim.anim2);
// anim1表示入場的Activity執(zhí)行的動畫
// anim2表示退場的Activity執(zhí)行的動畫

需要注意的是,這個動畫需要放到startActivity或者finish語句的后面,才會生效。

幀動畫

幀動畫就是按照既定順序去播放一組圖片的動畫效果,類似于電影膠片那種。
幀動畫比較簡單,效果也比較直觀,但是缺點也是顯而易見的,過多過大的圖片會急劇增大app體積,操作不當(dāng)還很容易OOM。
適用范圍一般,但是很好用的一種動畫效果,比如在下拉刷新時,表頭部分的動態(tài)小圖片。

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@mipmap/ic_money_off"
        android:duration="500" />
    <item
        android:drawable="@mipmap/ic_money_on"
        android:duration="500" />
</animation-list>

幀動畫資源文件存在于drawable文件夾中,使用時候,獲取對象并播放即可。

btn_anim.setBackgroundResource(R.drawable.anim_frame);
        final AnimationDrawable drawable = (AnimationDrawable) btn_anim.getBackground();
        btn_anim.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                drawable.start();
            }
        });

可以在代碼中設(shè)置背景,也可以在XML中設(shè)置。

屬性動畫

屬性動畫,理解為在一個時間間隔內(nèi),完成對象從一個屬性值到另一個屬性值的轉(zhuǎn)變。
屬性動畫的重點在屬性,是對對象的屬性的一種變換,操作重點在于數(shù)值變換,這與View動畫是完全不同的。
在看屬性動畫之前,請先暫時忘記View動畫以及幀動畫,因為他們不是一個層面上的東西。不能慣性的將屬性動畫理解為,對View動畫的擴(kuò)展,這樣理解出來的屬性動畫是非常局限的,因為他們雖然同為動畫,但是實現(xiàn)原理是完全不同的,使用屬性動畫可以做到很多View動畫做不到的有趣的特效。
屬性動畫曾經(jīng)有著嚴(yán)重的兼容問題,但現(xiàn)在app一般要求API16起步,這個問題早已不復(fù)存在。
屬性動畫也可以選用xml的方式或者代碼的方式來實現(xiàn),使用場景類似于View動畫。

屬性動畫的所屬文件夾為res/animator文件夾,注意,這與View動畫不是同一個文件夾,并且anim文件夾也不是animator的簡寫。
以下為xml構(gòu)建屬性動畫時,可能會用到的屬性。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together | sequentially">
    <!--
    ordering together 表示動畫同時播放 sequentially 表示動畫依次播放
    默認(rèn)值是together
    -->

    <objectAnimator
        android:duration="int"
        android:propertyName="string"
        android:repeatCount="int"
        android:repeatMode="restart | reverse"
        android:startOffset="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:valueType="colorType | intType | floatType" />

    <!--
        duration 表示動畫時長
        repeatCount 表示動畫重復(fù)次數(shù)
        repeatMode 表示動畫的重復(fù)模式,restart表示聯(lián)系重復(fù)播放,reverse表示一次正著播放,一次倒著
        startOffset 表示動畫的延遲時間
        valueFrom 表示屬性的起始值,允許的數(shù)據(jù)類型為float,int,color
        valueTo 表示屬性的結(jié)束值,允許的數(shù)據(jù)類型為float,int,color
        propertyName 指定做變換的屬性名
        valueType 制定做變換的屬性的值的類型,允許的類型為colorType,intType,floatType,如果是color,則該屬性不需要指定
        需要注意的是,如果propertyName 與valueType類型不匹配,可能會出現(xiàn)動畫無法執(zhí)行的情況。
    -->

    <animator
        android:duration="int"
        android:repeatCount="int"
        android:repeatMode="restart | reverse"
        android:startOffset="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:valueType="colorType | intType | floatType" />
    <!--
        animator 與 objectAnimator的區(qū)別僅僅是缺少propertyName屬性
    -->
    <set>
        ...
    </set>
</set>

可以看到,屬性動畫有兩個標(biāo)簽
<objectAnimator/>代表ObjectAnimator
<animator/>代表ValueAnimator
ObjectAnimator與ValueAnimator的關(guān)系是繼承的關(guān)系。
ObjectAnimator類作為ValueAnimator的子類,封裝了一些方便開發(fā)人員的方法,簡化了屬性動畫的實現(xiàn)。
ObjectAnimator類一般用于實現(xiàn)一些常見的動畫效果,例如旋轉(zhuǎn)評議縮放透明度等,ValueAnimator通過ValueAnimator.AnimatorUpdateListener接口,以及估值器,插值器,可以實現(xiàn)一些更加自由的效果。

ObjectAnimator

使用xml構(gòu)建一個屬性動畫的步驟與View動畫的步驟相似,以下是屬性動畫的xml文件。

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:propertyName="x"
    android:valueTo="200"
    android:valueType="floatType" />
---
Animator animator = AnimatorInflater.loadAnimator(mContext, R.animator.anim_object_anim);
//用objectAnimator也可以只不過需要強(qiáng)轉(zhuǎn)
animator.setTarget(tv_text);
animator.start();

或者是使用set集合的方式。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <objectAnimator
        android:duration="300"
        android:propertyName="x"
        android:valueTo="200"
        android:valueType="floatType" />
    <objectAnimator
        android:duration="300"
        android:propertyName="y"
        android:valueTo="200"
        android:valueType="floatType" />
</set>
---
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(mContext, R.animator.anim_object_anim);
set.setTarget(tv_text);
set.start();

再次強(qiáng)調(diào),如果xml文件中,propertyName 與valueType類型不匹配,可能會出現(xiàn)動畫無法執(zhí)行的情況。

或者也可以用代碼的方式實現(xiàn)一個屬性動畫。

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv_text, "x", 200);
objectAnimator.setDuration(3000);
objectAnimator.start();

ObjectAnimator中,可用的propertyName有以下幾個:
translationXtranslationY,控制位移
xy,控制位移
rotationrotationXrotationY,控制旋轉(zhuǎn)角度
scaleXscaleY,控制縮放
pivotXpivotY,控制以上旋轉(zhuǎn),縮放的中心點
alpha,控制透明度

以及詳細(xì)說明:

  1. translationX、translationY,x、y:
    這四個屬性作為一組來說明,x,y,是View左上角的坐標(biāo),其中,x = left + translationX,y = right + translationY。left,right指的是做動畫的View自身的layout_marginLeft,layout_marginRight。
    修改任何一個屬性都會使得控件位移,但是在移動細(xì)節(jié)上稍有差異。
    實例代碼以及圖1:
    ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(ll_text1, "translationX", 0);
    objectAnimator1.setDuration(3000);
    objectAnimator1.start();
    ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(ll_text2, "x", 0);
    objectAnimator2.setDuration(3000);
    objectAnimator2.start();

圖1 translationX與X.png

可以看到,對控件的x屬性做動畫的話,會忽略其自身的margin值

  1. rotation、rotationX、rotationY
    這三個屬性作為一組來說明,他們可以控制旋轉(zhuǎn)角度
    其中rotation控制著 2D 旋轉(zhuǎn)角度
    rotationXrotationY控制圍繞某樞軸點的 3D旋轉(zhuǎn)
    由于不好描述,放一個圖,圖2

    圖2 rotation與rotationX.png

    其中,text1為rotation旋轉(zhuǎn),text2為rotationX旋轉(zhuǎn)。

  2. scaleX、scaleY
    這兩個屬性控制著 View 圍繞某樞軸點的 2D 縮放比例,這沒什么好說的,就是常規(guī)縮放。

  3. pivotX、pivotY
    這兩個屬性控制著上文中,旋轉(zhuǎn)縮放的樞軸點的位置,缺省的樞軸點是控件的中心點

  4. alpha
    這個屬性表示控件的透明度。缺省值為 1 (不透明),為 0 則表示完全透明(看不見)

插值器與估值器

在ValueAnimator之前,先要說明插值器與估值器的概念,插值器的概念由于比較常見,不做過多說明也不影響理解,但是估值器的概念不很常見,所以放在這個位置一并說明。

TimeInterpolator,譯作插值器,作用是根據(jù)時間流逝的百分比來計算出當(dāng)前值改變的百分比,系統(tǒng)預(yù)置有9個插值器,他們的效果如下:

系統(tǒng)預(yù)置插值器

需要注意的是,插值器的特性,使得動畫計算的終點與起點有機(jī)會相同,如使用CycleInterpolator插值器或者自定義三角函數(shù)差值器,當(dāng)然這也是一個很好用的特性,使用時請保持想象力。

自定義插值器需要繼承Interpolator接口,也很簡單

public class MyInterpolator implements Interpolator {
    @Override
    public float getInterpolation(float v) {
        return (float) Math.sin(0.5 * Math.PI * v);
    }
}

代碼中,return后面公式部分決定了使用該插值器后的運動軌跡,插值器的公式就是簡單的二元方程式。
這里提供一個插值器公式的工具網(wǎng)站,http://inloop.github.io/interpolator,是github上的一個項目,建議保存方便以后使用。

插值器使用時,可以直接寫在xml中,或者在代碼中設(shè)置

android:interpolator="@android:anim/decelerate_interpolator"
---
objectAnimator.setInterpolator(new LinearInterpolator());

接下來是估值器
Evaluator,譯作估值器,作用是可以用來對int,float,color以外的類型做動畫。需要繼承TypeEvaluator接口,然后通過該接口來自定義估值算法以及返回值。
以下代碼是一個對對象做動畫的例子
估值器代碼

public class MyTypeEvaluator implements TypeEvaluator<TwoNumberBean> {

    @Override
    public TwoNumberBean evaluate(float v, TwoNumberBean startBean, TwoNumberBean endBean) {
        TwoNumberBean bean = new TwoNumberBean();
        bean.number1 = (int) (startBean.number1 + v * (endBean.number1 - startBean.number1));
        bean.number2 = (int) (startBean.number2 + v * (endBean.number2 - startBean.number2));
        return bean;
    }
}

其中,evaluate中,第一個參數(shù)表示估值小數(shù),例如總時長1000ms,現(xiàn)在運行了500ms,估值小數(shù)的值就是500/1000=0.5。第二、三個參數(shù)是開始值以及結(jié)束值。

以及使用該估值器的方法

ValueAnimator va = ValueAnimator.ofObject(new MyTypeEvaluator(), new TwoNumberBean(0, 0), new TwoNumberBean(100, 200));
va.setInterpolator(new AccelerateDecelerateInterpolator());
va.setDuration(5000);
va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {
        TwoNumberBean bean = (TwoNumberBean) valueAnimator.getAnimatedValue();
        tv_text1.setText(bean.number1 + "," + bean.number2);
    }
});
va.start();

以及運行效果


自定義估值器.gif

只是一個示例,估值器同樣需要想象力。

ValueAnimator

作為ObjectAnimator的父類,協(xié)同插值器,估值器,提供了更加多的可能性,當(dāng)然也更加復(fù)雜。
屬性動畫提供了兩個監(jiān)聽器,用于監(jiān)聽動畫的播放過程,主要有兩個接口

  1. AnimatorListener監(jiān)聽了動畫的開始,結(jié)束,取消,重復(fù)播放,四個狀態(tài),為了方便開發(fā),系統(tǒng)還提供了AnimatorListenerAdapter這個類,畢竟有時候用不到所有方法。
  2. AnimatorUpdateListener監(jiān)聽了整個動畫過程,每一幀都會調(diào)用一次該方法,比如上面那個例子。

對任意屬性做動畫

ValueAnimator 最靈活的一點是它可以對任意對象的任意屬性做動畫。
我們先看一個不是那么好的例子

public class LogBean {
    int i;

    public int getI() {
        Log.e("LogBean: ", "getI:_" + i);
        return i;
    }

    public void setI(int i) {
        Log.e("LogBean: ", "setI:_" + i);
        this.i = i;
    }
}
---
// 對該對象做動畫
ObjectAnimator.ofInt(new LogBean(),"i",500).setDuration(5000).start();

然后去看log輸出,可以看到LogBean的get、set方法被調(diào)用。
記不記得上文說過,“不能慣性的將屬性動畫理解為,對View動畫的擴(kuò)展”,因為屬性動畫是以一種更加靈活的方式實現(xiàn)的動畫,我曾經(jīng)因為這個思維慣性,對屬性動畫的理解偏差很大。
通過這個例子,我們可以得出以下結(jié)論:

  1. 被做動畫的對象必須要有駝峰命名的get,set方法,否則程序會崩潰。(底層是通過反射實現(xiàn)的)
  2. 屬性動畫對對象的某個屬性值的改變一定要能通過某種方法展現(xiàn)出來,一般開發(fā)時,當(dāng)然是通過改變UI,而這個例子是通過log。
    PS:如果展現(xiàn)不出來不就沒有意義了(~ ̄▽ ̄)~

當(dāng)然,在很多情況下,我們需要對一個對象做動畫時,往往不能滿足這兩個條件,官方文檔中有這么三種解決方案。

  1. 如果有權(quán)限的話,比如是自己寫的對象,可以給這個對象加上get、set方法。不過這個辦法一般來說沒用。
  2. 使用包裝類,為該對象添加get、set方法。
  3. 采用ValueAnimator ,并且通過接口監(jiān)聽動畫的整個過程,自己改變屬性。這是我最喜歡的一個辦法,畢竟靈活度非常高。上文中估值器的例子就是這個方法。

幾個需要注意的點

  1. 幀動畫的圖片不能太大,否則很容易OOM
  2. 如果屬性動畫設(shè)置為無限循環(huán),請在關(guān)閉當(dāng)前Activity時,務(wù)必關(guān)閉該動畫,否則會導(dǎo)致內(nèi)存泄漏,類似于Handler的內(nèi)存泄漏。但是View動畫并沒有這個問題。
  3. View動畫只是改變視圖,并不會真正改變View的狀態(tài)。
  4. View動畫有時會讓view.setVisibility(View.GONE)失效,只要調(diào)用view.clearAnimation()清除動畫即可。
  5. 可以的話,請開啟硬件加速。

個人理解,難免有錯誤紕漏,歡迎指正。轉(zhuǎn)載請注明出處。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容