1. 程式人生 > >Android動畫機制與使用技巧

Android動畫機制與使用技巧

導語

Android動畫效果一直是人機互動中十分重要的一部分,從早期的Android版本中,由於動畫機制和繪圖機制的不健全,Android的人機互動備受詬病,Android從4.X開始,特別是5.X,動畫越來越完善了,Google也開始重視這一方面了,當然我們也必須重視這一方面,看例項戳我

主要內容

  • Android View動畫框架
  • Android屬性動畫分析
  • Android佈局動畫
  • Interpolators(插值器)
  • 自定義動畫

具體內容

Android View動畫框架

Animation動畫框架定義了透明度,旋轉,縮放個移動等幾種動畫,而且控制了整個的View,實現原理是每次繪製檢視的時候View所在的ViewGroup中drawChild函式獲取該View的Animation的Transformation值,然後呼叫了canvas.concat(transformToApply.getMatrix()),通過矩陣運算完成動畫幀,如果動畫沒有完成則繼續呼叫invalidate()方法啟動下回繪製來驅動動畫,從而完成整個動畫的繪製。

檢視動畫使用簡單,效果豐富,它提供了AlphaAnimation,RotateAnimatio,TranslateAnimation,ScaleAnimation四種動畫方式,並提供了Animationset動畫集合,混合使用多種動畫,在Android3.0之前,檢視動畫一家獨大,但隨著Android3.0之後屬性動畫框架的推出它的風光就大不如前了。相比屬性動畫,檢視動畫的一個非常大的缺陷就是不具備互動性,當某個元件發生檢視動畫後,其響應事件的位置還依然在動畫前的地方,所以檢視動畫只能做普通的顯示效果,避免互動的發生,但是它的優點也非常明顯,即效率比較高且使用方便。檢視動畫使用非常簡單, 不僅可以通過XML檔案來描述一個動畫過程,同樣也可以使用程式碼來控制整個動畫過程。

下面這個例項就列舉了一些簡單的檢視動畫使用方法。

透明度動畫
AlphaAnimation al = new AlphaAnimation(0,1);
al.setDuration(2000);
alpha.startAnimation(al);
旋轉動畫
RotateAnimation ro = new RotateAnimation(0,300,100,100); ro.setDuration(2000); 
rotate.setAnimation(ro);
平移動畫
TranslateAnimation tr = new TranslateAnimation(0,200,0,300);
tr.setDuration(2000
); translate.setAnimation(tr);
縮放動畫
ScaleAnimation sc = new ScaleAnimation(0,2,0,2); sc.setDuration(2000); 
scale.setAnimation(sc);
動畫集合
AnimationSet setAnimation = new AnimationSet(true);
setAnimation.setDuration(2000);

AlphaAnimation als = new AlphaAnimation(0,1);
als.setDuration(2000);
setAnimation.addAnimation(als);

RotateAnimation ros = new RotateAnimation(0,300,100,100);
ros.setDuration(2000);
setAnimation.addAnimation(ros);

set.startAnimation(setAnimation);

我們一起來執行一下看效果:

動畫合集

當然,有動畫,就有監聽,我們來監聽一下動畫,以透明動畫為例。

AlphaAnimation al = new AlphaAnimation(0,1);
                al.setDuration(2000);
                alpha.startAnimation(al);

                al.setAnimationListener(new Animation.AnimationListener() {
                    @Override
                    public void onAnimationStart(Animation animation) {
                        Log.i("Animation","開始");
                    }

                    @Override
                    public void onAnimationEnd(Animation animation) {
                        Log.i("Animation","結束");
                        Toast.makeText(MainActivity.this,"動畫結束",Toast.LENGTH_LONG).show();
                    }

                    @Override
                    public void onAnimationRepeat(Animation animation) {

                    }
                });

執行之後列印log:

log

當然,我們眼見為實:

動畫監聽

通過監聽,我們就可以瞭解動畫的動向了。

Android屬性動畫分析

屬性動畫在Animator框架裡的,用的最多的也就是AnimatorSet和ObjectAnimator配合,使用ObjectAnimator進行更精細化的控制,只控制一個物件的屬性,而使用多個ObjectAnimator組合到AnimatorSet形成一個動畫,而且ObjectAnimator能夠自動驅動,單證各種好處,我們來看一下。

ObjectAnimator

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

平移動畫

在前面的講解中說到,以前的動畫框架所產生的動畫,並不能改變事件響應的位置,它只是單純地修改了顯示。如果使用舊的檢視動畫產生上面的效果,那麼按鈕的實際點選有效區依然在原來的地方,點選移動後的地方是不會有點選事件發生的。而屬性動圓則不同,由於它真實地改變了一個View的屬性,所以事件響應的區域也同樣發生了改變,這時候點選移動後的按鈕, 就會響應點選事件了。

讓我們來看看這個簡單的平移動畫是如何實現的,麻雀雖小五臟俱全,這個簡單的例子基本上就涵蓋了ObjectAnimator的所有知識。

ObjectAnimator ob = ObjectAnimator.ofFloat(
            view, 
            "translationX", 
            300);

ob.setDuration(2000);
ob.start();

通過ObjectAnimator的靜態工廠方法,建立個ObjectAnimator物件,第一個引數自然是要操縱的view,第二個引數則是要操縱的屬性而最後個引數是一個可變陣列引數,需要傳遞進去該屬性變化的一個取值過程。

不過,在使用ObjectAnimator的時候,有一點是非常重要的,就是操縱的set,get方法,不然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在後面的內容中會講到,這裡先來看看如何使用包裝類的方法給一個屬性增加set、get方法:

 private static class WrapperView {

        private View mTarget;

        public WrapperView(View target) {
            mTarget = target;
        }

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

        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }

通過上面的程式碼就給一個屬性包裝上了一層,並且提供了set、get方法,使用時只需要操作包裝類就可以間接呼叫到get、set方法了。

WrapperView vi = new WrapperView(mButton);
ObjectAnimator.ofInt(vi, "width", 500).setDuration(2000).start();
PropertyValuesHolder

這個類類似檢視動畫中的AnimationSet,就是把動畫給組合起來,在屬性動畫中,如果針對一個物件的多個屬性,就同時需要多個動畫了,可以使用PropertyValuesHolder,來實現,比如需要在平移的過程中,同時改變x、y的縮放,程式碼如下所示。

PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0,1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f,0,1f);
ObjectAnimator.ofPropertyValuesHolder(alpha,pvh1,pvh2,pvh3).setDuration(2000).start();
ValueAnimator

ValueAnimator這個屬性在動畫當中有很大的地位,雖然不想ObjectAnimator那樣,耀眼,但是他確實屬性動畫的核心所在,ObjectAnimator也是繼承自他。

public final class ObjectAnimator extends ValueAnimator

ValueAnimator本身不提供任何動畫,他更像是一個數值發生器,用來產生一定具有規律的數字,從而讓呼叫者控制動畫的整個過程,我們舉個例子來說明。

ValueAnimator va = ValueAnimator.ofFloat(0,100);
                va.setTarget(view);
                va.setDuration(2000).start();
                va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        float values = (float) animation.getAnimatedValue();
                        Log.i("數值",values+"");
                    }
                });

我們執行一下:

執行結果

動畫事件的監聽

一個完整的動畫是具有:start、repeat、end、cancel四個過程的,通過Android的介面,我們很容易監聽到這幾個事件。

 ob.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) {

                    }
                });

當然,大部分的場景嗎,我們只關心動畫結束,所以,Android也提供了一個AnimatorLisistenerAdapter來讓你自己選擇監聽事件。

va.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        super.onAnimationEnd(animation);
                    }
                });
AnimatorSet

對於一個屬性同時作用在一個view上,前面已經有一個PropertyValuesHolder了,而AnimatorSet不僅能實現,而且能更精準的控制順序,同樣是實現PropertyValuesHolder的動畫,AnimatorSet是這樣實現的:

ObjectAnimator animator1 = ObjectAnimator.ofFloat(alpha, "translationX", 300f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(alpha, "scaleX", 1f, 0, 1f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(alpha, "scaleY", 1f, 0, 1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(2000);
set.playTogether(animator1,animator2,animator3);
set.start();

在屬性動畫中,AnimatorSet正是通過playTogether等方法控制多個動畫協同工作,從而控制播放順序的。

在XML中定義動畫

屬性動畫同樣的可以定義在xml中,我們也去玩玩:

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

</objectAnimator>

在程式碼中引用:

    /**
     * 引用xml動畫
     * @param v
     */
    private void scaleX(View v){
        Animator anim = AnimatorInflater
                .loadAnimator(this,R.animator.animator);
        anim.setTarget(v);
        anim.start();
    }
View的animate方法

在Android3.0,Google給view增加了animate方法直接來驅動屬性動畫,程式碼如下,我們可以發現,其實animate就是屬性動畫的一種縮寫。

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

                    }
                }).withEndAction(new Runnable() {
                    @Override
                    public void run() {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {

                            }
                        });
                    }
                }).start();

Android佈局動畫

所謂的佈局動畫,就是作用在ViewGruop中給View新增的過渡效果,最簡單的方法是在xml中開啟。

android:animateLayoutChanges="true"

不過這都是Android自帶的效果,渣渣。

我們還可以通過LayoutAnimationController來定義。

ll = (LinearLayout) findViewById(R.id.ll);
//設定過渡動畫
ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1);
sa.setDuration(2000);
LayoutAnimationController lc = new LayoutAnimationController(sa, 0.5f);
lc.setOrder(LayoutAnimationController.ORDER_NORMAL);
//設定佈局動畫
ll.setLayoutAnimation(lc);

通過上面。就可以給佈局正加一個檢視動畫,讓子View出現的時候有一個縮放動畫,在apiDemo中這個例子還是很經典的。

LayoutAnimationController的第一個引數是需要作用的動畫,而第二個引數,剛是每個子View顯示的delay的時間。當delay時間不為0時,可以設定子View顯示的順序:
- LayoutAnimationController.ORDER_NORMAL——順序。
- LayoutAnimationController.ORDER_RANDOM——隨機。
- LayoutAnimationController.ORDER_REVEESE——反序。

Interpolators(插值器)

插值器在動畫中是一個非常重要的概念,我們通過插值器可以定義動畫變換速率,這一點非常類似物理中的加速度,其作用主要是目標變化對應的變化,同樣的一個動畫變換起始值,在不同的插值器的作用下,每個單位時間內所達到的變換值都是不一樣的,例如一個平移動畫,如果使用線性插值器,那麼在持續時間內單位時間所移動的距離都是一樣的,如果使用加速度插值器,那麼單位時間內所移動的速度越來越快,大家如果把插值器的概念理解為一個人進行萬米長跑,規定一個小時到達,有的人怕時間來不及一開始就加速跑但是到後面速度越來越慢,而有的人開始節省體力,所以開始跑的比較慢,後來越跑越快直到終點,不管怎麼跑,最終他們的都是在規定的時間到達終點,唯一不同的是他們的跑的速度不同,通過這個例子,我們可以很好的理解插值器的概念。

插值器

自定義動畫

建立自定義動畫很簡單,只需要實現applyTransformation的邏輯就可以,不過通常情況下,我們還要覆蓋父類的initialize方法來完成一些初始化工作。

@Overrideprotected void applyTransformation(float interpolatedTime, Transformation t) {
    super.applyTransformation(interpolatedTime, t);
}

第一個引數interpolatedTime是前面說的插值器的時間因子,這個因子是由動畫當前完成的百分比和當前時間對應的差值計算的,取值範圍在0-1.0,第二個引數就非常簡單了,她是矩陣的封裝類,一般使用這個類獲取當前的矩陣物件,程式碼如下:

 Matrix matrix = t.getMatrix();

通過改變獲得的matrix 物件,可以將動畫效果實現,而對於matrix 的變換操作,基本上可以實現任何效果,我們實現一個電視機關閉的效果。

 @Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    Matrix matrix = t.getMatrix();
    matrix.preScale(1, 1 - interpolatedTime, width,height);
    super.applyTransformation(interpolatedTime, t);
}

當然我們可以設定更加精準的插值器,從而對不同的過程採用不同的動畫效果,模擬的更加逼真。

接下來我們結合矩陣,並且使用Canmera來實現一個3D的效果,要注意的是,這裡所指的Camera不是相機,而是這個類,他封裝了openGl的3D動畫,我們繼續用程式碼來實現。

    @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());
        w = width / 2;
        h = height / 2;

    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {

        Matrix matrix = t.getMatrix();
    //  matrix.preScale(1, 1 - interpolatedTime, w,h);
        mCamera.save();
        //設定旋轉角度
        mCamera.rotate(0, 180, 360);
        mCamera.restore();
        //通過pre方法設定矩形作用前的偏移量來改變旋轉中心
        matrix.preTranslate(w, h);
        matrix.postTranslate(-w, -h);

        super.applyTransformation(interpolatedTime, t);

    }

通過以上的方法,就可以實現了。

總結

  • 瞭解Android動畫框架,會使用Android簡單檢視動畫。
  • 學會使用Android屬性動畫開發出更加豐富的動畫效果。
  • 簡單使用佈局動畫。
  • 使用Interpolators(插值器)自定義動畫。