Android全套動畫使用技巧

1008711發表於2019-03-02

一、Android View 動畫框架

Animation框架定義了透明度、旋轉、縮放和位移幾種常見的動畫,控制的整個View,實現原理是每次繪製檢視時View所在ViewGroup中的drawChild函式獲取該View的Animation的Transformation值,然後呼叫canvas.concat(transformToApply.getMatrix()),通過矩陣運算完成動畫幀。如果沒有完成就繼續呼叫invalidate()函式,啟動下次繪製來驅動動畫,從而完成整個動畫的繪製。
檢視動畫使用簡單,效果豐富,它提供了AlphaAnimation、RotateAnimation、TranslateAnimation、ScaleAnimation四種動畫方式,並提供動畫集合AnimationSet,混合使用多種動畫。在Android3.0之前,檢視動畫一家獨大,但隨著Android3.0之後屬性動畫框架的推出,它的風光就大不如從前。相比屬性動畫,檢視動畫的一個非常大的缺陷就是不具備互動性,當某個元素髮生檢視動畫後,其響應事件的位置還依然在動畫前的地方,所以檢視動畫只能做普通的動畫效果,避免互動的發生。但是它的優點也非常明顯,即效率比較高且使用方便。

檢視動畫使用非常簡單,不僅可以通過XML檔案來描述一個動畫過程,同樣也可以使用程式碼來控制整個動畫過程。

(1)、透明度動畫

為檢視增加透明度的變換動畫。

AlphaAnimation aa = new AlphaAnimation(0, 1);
aa.setDuration(1000);
view.startAnimation(aa);複製程式碼

(2)、旋轉動畫

為檢視增加旋轉的變換動畫。

RotateAnimation ra = new RotateAnimation(0, 360, 100, 100);
ra.setDuration(1000);
view.startAnimation(ra);複製程式碼

其引數分別為旋轉的起始角度和旋轉中心點的座標,當然,可以通過設定引數來控制旋轉動畫的參考系,這裡設定旋轉動畫的參考系為中心。

RotateAnimation ra1 = new RotateAnimation(0, 360, RotateAnimation.RELATIVE_TO_SELF, 0.5F, RotateAnimation.RELATIVE_TO_SELF, 0.5F);複製程式碼

(3)、位移動畫

為檢視移動時增加位移動畫。

TranslateAnimation ta = new TranslateAnimation(0, 200, 0, 300);
ta.setDuration(1000);
view.startAnimation(ta);複製程式碼

(4)、縮放動畫

為檢視的縮放增加動畫效果

ScaleAnimation sa = new ScaleAnimation(0, 2, 0, 2);
sa.setDuration(1000);
view.startAnimation(sa);複製程式碼

與旋轉動畫一樣,縮放動畫也可以設定羅芳的中心點,設定中心為自身中心效果

ScaleAnimation sa1 = new ScaleAnimation(0, 1, 0, 1, Animation.RELATIVE_TO_SELF, 0.5F, Animation.RELATIVE_TO_SELF, 0.5F);
sa1.setDuration(1000);
view.startAnimation(sa1);複製程式碼

(5)、動畫集合

通過AnimationSet,可以將動畫以組合的形式展現出來:

AnimationSet as = new AnimationSet(true);
as.setDuration(1000);

AlphaAnimation aa = new AlphaAnimation(0, 1);
aa.setDuration(1000);
as.addAnimation(aa);


RotateAnimation ra = new RotateAnimation(0, 360, 100, 100);
ra.setDuration(1000);
as.addAnimation(ra);



TranslateAnimation ta = new TranslateAnimation(0, 200, 0, 300);
ta.setDuration(1000);
as.addAnimation(ta);

ScaleAnimation sa = new ScaleAnimation(0, 2, 0, 2);
sa.setDuration(1000);
as.addAnimation(sa);

view.startAnimation(as);複製程式碼

可以直接拷貝執行程式碼看效果!

對於動畫事件,Android也提供了對應的監聽回撥,程式碼:

as.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
        //動畫開始
    }

    @Override
    public void onAnimationEnd(Animation animation) {
        //動畫結束
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
        //動畫重複
    }
});複製程式碼

二、屬性動畫

由於Android3.0之前已有的動畫框架Animation存在一些侷限性——動畫改變的只是顯示,並不能響應事件。因此在Android3.0之後,Google就提出了屬性動畫這樣一個新的動畫框架,實現更豐富的效果。

而在Animator框架中使用最多的就是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator進行更精細化控制,只控制一個物件的一個屬性值,而使用多個ObjectAnimator組合到AnimatorSet形成一個動畫。而且ObjectAnimator能夠自動驅動,可以呼叫setFrameDelay(long frameDelay)設定動畫幀之間的間隙時間。最重要的是,屬性動畫通過呼叫屬性的get、set方法來真實地控制了一個View的屬性值,因此強大的屬性動畫框架,基本可以實現所有的動畫效果。

(1)、ObjectAnimator

ObjectAnimator是屬性動畫框架中最重要的實行類,建立一個ObjectAnimator只需要通過他的靜態工廠類直接返回一個ObjectAnimator物件。引數包括一個物件和物件的屬性名字,但這個屬性也必須有get和set函式,內部會通過Java反射機制來呼叫set函式修改物件屬性值。同樣,你也可以呼叫setInterpolator設定相應的差值器。

接下來試想一下對一個Button新增一個平移動畫,使用以前的動畫框架平移後將不能觸發點選事件,點選的有效區域仍然是原來的地方,點選移動後的地方是不會有點選事件發生的。而屬性動畫則不同,它真實地改變了一個View的屬性,所以事件響應的區域也同樣發生了改變,這時候點選移動後的按鈕,就會響應點選事件了。

屬性動畫平移程式碼如下:

ObjectAnimator animator = ObjectAnimator.ofFloat(
        imageView,
        "translationX",
        200F);
animator.setDuration(300);
animator.start();複製程式碼

在使用ObjectAnimator的時候,有一點非常重要,那就是要操縱的屬性必須具有get、set方法,不然ObjectAnimator就無法生效。下面是常用的屬性:

  • translationX和translationY:這兩個屬性作為一種增量控制著View物件從它佈局容器左上角座標開始的位置。
  • rotation、rotationX和rotationY:這個三個屬性控制View物件圍繞支點進行2D和3D旋轉。
  • scaleX和scaleY:這兩個屬性控制著View物件圍繞他的支點進行2D縮放。
  • pivotX和pivotY:這兩個屬性控制著View物件的支點位置,圍繞這個支點進行旋轉和縮放變換處理。預設情況下,該支點的位置就是View物件的中心點。
  • x和y:這兩個簡單實用的屬性,描述了View物件在它的容器中的最終位置,它是最初的左上角座標和translationX、translationY值的累積和。
  • alpha:表示View物件的alpha透明度。預設值是1(不透明),0代表完全透明(不可見)。

根據以上得知檢視動畫所實現的動畫效果,這裡基本都已經包含了。

那麼如果一個屬性沒有get、set方法,屬性動畫是不是就束手無策了呢?答案是否定的,Google在應用層提供了兩種方案來解決這個問題,一個是通過自定義一個屬性類或者包裝類,來間接地給這個屬性增加get、set方法;或者通過ValueAnimator來實現,ValueAnimator在後面的內容中講到,這個先看看使用包裝類的方法給一個屬性增加get、set方法,程式碼如下:

private static class WrapperView {
    private View mTarget;

    public WrapperView(View mTarget) {
        this.mTarget = mTarget;
    }

    public int getWidth() {
        return mTarget.getLayoutParams().width;
    }

    public void setWidth(int width) {
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}複製程式碼

通過以上程式碼,就跟一個屬性包裝了一層,並給它提供了get、set方法。使用時只需要操縱包裝類就可以間接呼叫到get、set方法了,程式碼如下所示:

WrapperView wrapperView = new WrapperView(view);
ObjectAnimator.ofInt(wrapperView, "width", 500).setDuration(5000).start();複製程式碼

(2)、PropertyValuesHolder

類似檢視動畫中的AnimationSet,在屬性動畫中,如果針對同一個物件的多個屬性,要同時作用多種動畫,可以使用PropertyValuesHolder來實現。比如平移動畫,如果在平移的過程中同時改變X、Y軸的縮放,可以這樣實現,程式碼:

PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX", 300);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(pvh1, pvh2, pvh3).setDuration(1000).start();複製程式碼

在程式碼中,分別使用PropertyValuesHolder 物件控制translationX、scaleX、scaleY這三個屬性,最屌呼叫ObjectAnimator.ofPropertyValuesHolder方法實現多屬性動畫的共同作用,整個實現方法非常類似AnimatorSet使用。

(3)、ValueAnimator

ValueAnimator在屬性動畫中佔用非常重要的地位,雖然不ObjectAnimator那樣耀眼,但它卻是屬性動畫的核心所在,ObjectAnimator也是繼承自ValueAnimator。

public final class ObjectAnimator extends ValueAnimator複製程式碼

ValueAnimator本身不提供任何動畫效果,它更像一個數值發生器,用來產生具有一定規律的數字,從而讓呼叫者來控制動畫的實現過程,ValueAnimator的一般使用方法:通常在ValueAnimator的AnimatorUpdateListener中監聽數值的變換,完成動畫的變換。

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 100);
valueAnimator.setTarget(imageView);
valueAnimator.setDuration(1000).start();
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Float value = (Float) animation.getAnimatedValue();
    }
});複製程式碼

(4)、動畫事件的監聽

一個完整的動畫具有Start、Repeat、End、Cancel四個過程,通過Android提供了介面,很方便地監聽到這四個事件:

ObjectAnimator anim = ObjectAnimator.ofFloat(imageView, "alpha", 0.5F);
anim.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        
    }

    @Override
    public void onAnimationEnd(Animator animation) {

    }

    @Override
    public void onAnimationCancel(Animator animation) {

    }

    @Override
    public void onAnimationRepeat(Animator animation) {

    }
});
anim.start();複製程式碼

大部分的時候只關心onAnimationEnd事件,所以Android也提供了一個AnimatorListenerAdapter來讓我們選擇必要的事件進行監聽:

anim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
    }
});複製程式碼

(5)、AnimatorSet

對於一個屬性同時作用多個屬性動畫效果,前面已經使用PropertyValuesHolder實現了這樣的效果。而AnimatorSet不僅能實現這樣的效果,同時也能實現更為精確的順序控制。同樣是實現上面使用PropertyValuesHolder演示的那個動畫效果,如果使用AnimatorSet來實現,那麼程式碼如下:

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "translationX", 300f);
ObjectAnimator objectAnimator1 = ObjectAnimator.ofFloat(imageView, "scaleX", 1f, 0f, 1f);
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView, "scaleY", 1f, 0f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(1000);
animatorSet.playTogether(objectAnimator, objectAnimator1, objectAnimator2);
animatorSet.start();複製程式碼

在屬性動畫中,AnimatorSet正是通過playTogether()、playSquentially()、animSet.play().width()、defore()、after()這些方法來控制多個動畫的協同工作方式,從而做到對動畫播放順序的精確控制。

(6)、在XML中使用屬性動畫

屬性動畫同檢視動畫一樣,也可以直接寫在XML檔案中,程式碼:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="scaleX"
    android:valueFrom="1.0"
    android:valueTo="2.0"
    android:valueType="floatType">

</objectAnimator>複製程式碼

前提使用XML定義屬性動畫XML檔案一定要放在res/animator/filename.xml資料夾下面才能識別,否則不能識別。發現屬性動畫與檢視動畫在XML檔案中的寫法很相似。在程式中使用:

Animator anim = AnimatorInflater.loadAnimator(this,R.animator.filename);
anim.setTarget(view);
anim.start();複製程式碼

(7)、View的animate方法

在Android3.0之後,Google給View增加了animate方法來直接驅動屬性動畫,程式碼如下:

imageView.animate()
        .alpha(0)
        .y(300)
        .setDuration(300)
        .withStartAction(new Runnable() {
            @Override
            public void run() {

            }
        })
        .withEndAction(new Runnable() {
            @Override
            public void run() {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        
                    }
                });
            }
        }).start();複製程式碼

三、Android佈局動畫

佈局動畫是指作用在ViewGroup上,給ViewGroup增加View時新增一個動畫過渡效果。最簡單的佈局動畫是在ViewGroup的XML中,使用如下程式碼開啟佈局動畫:

android:animateLayoutChanges="true"複製程式碼

通過以上設定,當ViewGroup新增到View時,子View會呈現逐漸顯示的過渡效果,不過這個效果是Android預設的顯示的過渡效果,無法使用自定義動畫來替換這個效果。

還可以通過使用LayoutAnimatorController類自定義一個子View的過渡效果,新增一個檢視動畫,使得子View出現的時候有一個縮放的動畫效果,程式碼:

LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1);
sa.setDuration(2000);
//設定佈局動畫的顯示
LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5f);
//設定佈局動畫
ll.setLayoutAnimation(lac);複製程式碼

LayoutAnimationController 的第一個引數,是需要作用的動畫,而第二個引數,則是每個子View顯示的delay時間。當delay時間不為0時,可以設定子View顯示的順序。

//順序
public static final int ORDER_NORMAL  = 0;

//隨機
public static final int ORDER_REVERSE = 1;

//反序
public static final int ORDER_RANDOM  = 2;複製程式碼

四、Interpolators——插值器

插值器是動畫一個非常重要的概念,通過插值器Interpolators,可以定義動畫變換速率,這一點非常類似物理中的加速度,起作用主要是控制目標變數的變化值進行對應的變化。

  • AccelerateDecelerateInterpolator 在動畫開始與介紹的地方速率改變比較慢,在中間的時候加速
  • AccelerateInterpolator 在動畫開始的地方速率改變比較慢,然後開始加速
  • AnticipateInterpolator 開始的時候向後然後向前甩
  • AnticipateOvershootInterpolator 開始的時候向後然後向前甩一定值後返回最後的值
  • BounceInterpolator 動畫結束的時候彈起
  • CycleInterpolator 動畫迴圈播放特定的次數,速率改變沿著正弦曲線
  • DecelerateInterpolator 在動畫開始的地方快然後慢
  • LinearInterpolator 以常量速率改變
  • OvershootInterpolator 向前甩一定值後再回到原來位置
  • PathInterpolator 路徑插值器

五、自定義動畫

建立自定義動畫只需要實現它的applyTransformation的邏輯就可以了,不過通常情況下,還需要覆蓋父類的initalize方法來實現初始化工作。applyTransformation方法如下:

protected void applyTransformation(
        float interpolatedTime,
        Transformation t) {
}複製程式碼

第一個引數interpolatedTime是上面說的插值器的時間因子,這個因子是由動畫當前完成的百分比和當前時間所對應的外掛所計算得來的,取值範圍在0-1.0。

第二個引數Transformation 是矩陣的封裝類,一般使用這個類來獲得當前的局針對想,如下:

final Matrix matrix = t.getMatrix();複製程式碼

通過改變獲得的matrix物件,可以將動畫效果實現出來,而對於matrix的變換操作,基本可以實現任何效果的動畫。對於matrix的介紹可以檢視:深入理解 Android 中的 Matrix

@Override
protected void applyTransformation(
        float interpolatedTime,
        Transformation t) {
    final Matrix matrix = t.getMatrix();
    //通過matrix的各種操作來實現動畫
}複製程式碼

通過模擬電視機關閉的效果來看看簡單的矩陣變化時如何實現動畫效果的。電視機關閉的效果就是讓一個圖片縱向比例不斷縮小即可,對應的矩陣動處理方法如下:

@Override
protected void applyTransformation(
        float interpolatedTime,
        Transformation t) {
    final Matrix matrix = t.getMatrix();
    matrix.preScale(1,
            1 - interpolatedTime,
            mCenterWidth,
            mCenterHeight);
}複製程式碼

其中的mCenterWidth、mCenterHeight即為縮放的中心點,設定為圖片中心即可。這樣通過一個簡單的矩陣變換,就可以模擬電視機關閉的動畫。

也可以設定更精準插值器,並將0到1的時間因子拆分成不同的過程,從而對不同的過程採用不同的動畫效果。程式碼如下:

@Override
public void initialize(int width,
                       int height,
                       int parentWidth,
                       int parentHeight) {

    super.initialize(width, height, parentWidth, parentHeight);
    // 設定預設時長
    setDuration(2000);
    // 動畫結束後保留狀態
    setFillAfter(true);
    // 設定預設插值器
    setInterpolator(new BounceInterpolator());
    mCenterWidth = width / 2;
    mCenterHeight = height / 2;
}複製程式碼

自定義動畫的核心——如何定義動畫的進行過程,程式碼:

@Override
protected void applyTransformation(
        float interpolatedTime,
        Transformation t) {
    final Matrix matrix = t.getMatrix();
    mCamera.save();
    // 使用Camera設定旋轉的角度
    mCamera.rotateY(mRotateY * interpolatedTime);
    // 將旋轉變換作用到matrix上
    mCamera.getMatrix(matrix);
    mCamera.restore();
    // 通過pre方法設定矩陣作用前的偏移量來改變旋轉中心
    matrix.preTranslate(mCenterWidth, mCenterHeight);
    matrix.postTranslate(-mCenterWidth, -mCenterHeight);
}複製程式碼

通過以上程式碼使用Camera類實現動畫效果就是設定三個座標軸的旋轉角度,通過最後兩行程式碼可以改變旋轉時預設旋轉中心。

未完,待續。。。

相關文章