Android屬性動(dòng)畫(Property Animation)

參考于:http://blog.csdn.net/guolin_blog/article/details/43536355

動(dòng)畫簡(jiǎn)介


android提供大致三種動(dòng)畫類型:View Animation, Drawable Animation 、Property Animation 。

  • View Animation:
    1. 一般只能修改組件(View Object)的部分屬性,比如:scaling(大小)和rotation(旋轉(zhuǎn)),但是無(wú)法修改組件的背景顏色,支持簡(jiǎn)單的縮放、平移、旋轉(zhuǎn)、透明度基本的動(dòng)畫,且有一定的局限性,但是當(dāng)View Animation能方便快速地解決需求時(shí),選擇它也是不錯(cuò)的選擇。
  1. View Animation使某個(gè)組件產(chǎn)生動(dòng)畫效果移動(dòng)一段距離后,比如從屏幕左側(cè)移動(dòng)到右側(cè),其實(shí)整個(gè)過(guò)程是繪制出來(lái)的效果,該組件真正的位置依然保留在左側(cè),只有點(diǎn)擊左側(cè)位置才能觸發(fā)該組件。所以想真正移動(dòng)某組件,需要在動(dòng)畫結(jié)束后添加代碼實(shí)現(xiàn)。
  • Property Animation可以修改任何對(duì)象(View Object 或者 non-view Object)的任何屬性,比如大小,旋轉(zhuǎn),顏色。并且,移動(dòng)后的組件,位置也回跟隨著改變。
  • Drawable Animation是逐幀動(dòng)畫,那么使用它之前必須先定義好各個(gè)幀。其實(shí)就是將一個(gè)完整的動(dòng)畫拆分成一張張單獨(dú)的圖片,然后再將它們連貫起來(lái)進(jìn)行播放,類似于動(dòng)畫片的工作原理。

Property Animation 相關(guān)屬性


Property Animation故名思議就是通過(guò)動(dòng)畫的方式改變對(duì)象的屬性了,我們首先需要了解幾個(gè)屬性:

  • Duration動(dòng)畫的持續(xù)時(shí)間,默認(rèn)300ms。
  • Time interpolation:時(shí)間差值,乍一看不知道是什么,但是我說(shuō)LinearInterpolator、AccelerateDecelerateInterpolator,大家一定知道是干嘛的了,定義動(dòng)畫的變化率。
  • Repeat count and behavior:重復(fù)次數(shù)、以及重復(fù)模式;可以定義重復(fù)多少次;重復(fù)時(shí)從頭開始,還是反向。
  • Animator sets: 動(dòng)畫集合,你可以定義一組動(dòng)畫,一起執(zhí)行或者順序執(zhí)行。
  • Frame refresh delay:幀刷新延遲,對(duì)于你的動(dòng)畫,多久刷新一次幀;
    默認(rèn)為10ms,但最終依賴系統(tǒng)的當(dāng)前狀態(tài);基本不用管。

相關(guān)的類


  • ObjectAnimator 動(dòng)畫的執(zhí)行類
  • ValueAnimator 動(dòng)畫的執(zhí)行類
  • AnimatorSet 用于控制一組動(dòng)畫的執(zhí)行:線性,一起,每個(gè)動(dòng)畫的先后執(zhí)行等。
  • AnimatorInflater 用戶加載屬性動(dòng)畫的xml文件
  • TypeEvaluator 類型估值,主要用于設(shè)置動(dòng)畫操作屬性的值。
  • TimeInterpolator 時(shí)間插值,上面已經(jīng)介紹。
    總的來(lái)說(shuō),屬性動(dòng)畫就是,動(dòng)畫的執(zhí)行類來(lái)設(shè)置動(dòng)畫操作的對(duì)象的屬性、持續(xù)時(shí)間,開始和結(jié)束的屬性值,時(shí)間差值等,然后系統(tǒng)會(huì)根據(jù)設(shè)置的參數(shù)動(dòng)態(tài)的變化對(duì)象的屬性。

ValueAnimator


ValueAnimator是整個(gè)屬性動(dòng)畫機(jī)制當(dāng)中最核心的一個(gè)類,屬性動(dòng)畫的運(yùn)行機(jī)制是通過(guò)不斷地對(duì)值進(jìn)行操作來(lái)實(shí)現(xiàn)的,而初始值和結(jié)束值之間的動(dòng)畫過(guò)渡就是由ValueAnimator這個(gè)類來(lái)負(fù)責(zé)計(jì)算的。它的內(nèi)部使用一種時(shí)間循環(huán)的機(jī)制來(lái)計(jì)算值與值之間的動(dòng)畫過(guò)渡,我們只需要將初始值和結(jié)束值提供給ValueAnimator,并且告訴它動(dòng)畫所需運(yùn)行的時(shí)長(zhǎng),那么ValueAnimator就會(huì)自動(dòng)幫我們完成從初始值平滑地過(guò)渡到結(jié)束值這樣的效果。除此之外,ValueAnimator還負(fù)責(zé)管理動(dòng)畫的播放次數(shù)、播放模式、以及對(duì)動(dòng)畫設(shè)置監(jiān)聽器等,確實(shí)是一個(gè)非常重要的類。

例子:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.start();

調(diào)用ValueAnimator的ofFloat()方法就可以構(gòu)建出一個(gè)ValueAnimator的實(shí)例,ofFloat()方法當(dāng)中允許傳入多個(gè)float類型的參數(shù),這里傳入0和1就表示將值從0平滑過(guò)渡到1,然后調(diào)用ValueAnimator的setDuration()方法來(lái)設(shè)置動(dòng)畫運(yùn)行的時(shí)長(zhǎng),最后調(diào)用start()方法啟動(dòng)動(dòng)畫。

另外ofFloat()方法當(dāng)中是可以傳入任意多個(gè)參數(shù)的,因此我們還可以構(gòu)建出更加復(fù)雜的動(dòng)畫邏輯,比如說(shuō)將一個(gè)值在5秒內(nèi)從0過(guò)渡到5,再過(guò)渡到3,再過(guò)渡到10,就可以這樣寫:



可能你只是希望將一個(gè)整數(shù)值從0平滑地過(guò)渡到100,那么也很簡(jiǎn)單,只需要調(diào)用ValueAnimator的ofInt()方法就可以了

ValueAnimator anim = ValueAnimator.ofInt(0, 100);

ofFloat()方法的第二個(gè)參數(shù)到底可以傳哪些值呢?后面我們將使用alpha、rotation、translationX和scaleY這幾個(gè)值,分別可以完成淡入淡出、旋轉(zhuǎn)、水平移動(dòng)、垂直縮放這幾種動(dòng)畫,那么還有哪些值是可以使用的呢?其實(shí)這個(gè)問題的答案非常玄乎,就是我們可以傳入任意的值到ofFloat()方法的第二個(gè)參數(shù)當(dāng)中。任意的值?相信這很出乎大家的意料吧,但事實(shí)就是如此。因?yàn)镺bjectAnimator在設(shè)計(jì)的時(shí)候就沒有針對(duì)于View來(lái)進(jìn)行設(shè)計(jì),而是針對(duì)于任意對(duì)象的,它所負(fù)責(zé)的工作就是不斷地向某個(gè)對(duì)象中的某個(gè)屬性進(jìn)行賦值,然后對(duì)象根據(jù)屬性值的改變?cè)賮?lái)決定如何展現(xiàn)出來(lái)。

那么比如說(shuō)我們調(diào)用下面這樣一段代碼:

ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);

其實(shí)這段代碼的意思就是ObjectAnimator會(huì)幫我們不斷地改變textview對(duì)象中alpha屬性的值,從1f變化到0f。然后textview對(duì)象需要根據(jù)alpha屬性值的改變來(lái)不斷刷新界面的顯示,從而讓用戶可以看出淡入淡出的動(dòng)畫效果。

那么textview對(duì)象中是不是有alpha屬性這個(gè)值呢?沒有,不僅textview沒有這個(gè)屬性,連它所有的父類也是沒有這個(gè)屬性的!這就奇怪了,textview當(dāng)中并沒有alpha這個(gè)屬性,ObjectAnimator是如何進(jìn)行操作的呢?其實(shí)ObjectAnimator內(nèi)部的工作機(jī)制并不是直接對(duì)我們傳入的屬性名進(jìn)行操作的,而是會(huì)去尋找這個(gè)屬性名對(duì)應(yīng)的get和set方法,因此alpha屬性所對(duì)應(yīng)的get和set方法應(yīng)該就是:

public void setAlpha(float value); public float getAlpha();

那么textview對(duì)象中是否有這兩個(gè)方法呢?確實(shí)有,并且這兩個(gè)方法是由View對(duì)象提供的,也就是說(shuō)不僅TextView可以使用這個(gè)屬性來(lái)進(jìn)行淡入淡出動(dòng)畫操作,任何繼承自View的對(duì)象都可以的。

既然alpha是這個(gè)樣子,相信大家一定已經(jīng)明白了,前面我們所用的所有屬性都是這個(gè)工作原理,那么View當(dāng)中一定也存在著setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當(dāng)中去找一下。

ObjectAnimator


相比于ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因?yàn)?strong>ValueAnimator只不過(guò)是對(duì)值進(jìn)行了一個(gè)平滑的動(dòng)畫過(guò)渡,但我們實(shí)際使用到這種功能的場(chǎng)景好像并不多。而ObjectAnimator則就不同了,它是可以直接對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫操作的,比如說(shuō)View的alpha屬性。

不過(guò)雖說(shuō)ObjectAnimator會(huì)更加常用一些,但是它其實(shí)是繼承自ValueAnimator的,底層的動(dòng)畫實(shí)現(xiàn)機(jī)制也是基于ValueAnimator來(lái)完成的,因此ValueAnimator仍然是整個(gè)屬性動(dòng)畫當(dāng)中最核心的一個(gè)類。那么既然是繼承關(guān)系,說(shuō)明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它們的用法也非常類似,這里如果我們想要將一個(gè)TextView在5秒中內(nèi)從常規(guī)變換成全透明,再?gòu)娜该髯儞Q成常規(guī),就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "alpha", 1f, 0f, 1f);
 animator.setDuration(5000);
 animator.start();

效果:



ofFloat()方法,這里第一個(gè)參數(shù)要求傳入一個(gè)object對(duì)象,我們想要對(duì)哪個(gè)對(duì)象進(jìn)行動(dòng)畫操作就傳入什么,這里我傳入了一個(gè)textview。第二個(gè)參數(shù)是想要對(duì)該對(duì)象的哪個(gè)屬性進(jìn)行動(dòng)畫操作,由于我們想要改變TextView的不透明度,因此這里傳入"alpha"。后面的參數(shù)就是不固定長(zhǎng)度了,想要完成什么樣的動(dòng)畫就傳入什么值,這里傳入的值就表示將TextView從常規(guī)變換成全透明,再?gòu)娜该髯儞Q成常規(guī)。

再例如想要將TextView進(jìn)行一次360度的旋轉(zhuǎn),就可以這樣寫:


ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);
animator.setDuration(2000);
animator.start();

效果:


如果想要將TextView先向左移出屏幕,然后再移動(dòng)回來(lái),就可以這樣寫:

float x = textview.getTranslationX();
ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "translationX", x, -500f, x);
animator.setDuration(5000);
animator.start();

效果:


textView進(jìn)行縮放操作,比如說(shuō)將TextView在垂直方向上放大3倍再還原,就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textView, "scaleY", 1f, 3f, 1f);
animator.setDuration(5000);
animator.start();

效果:


組合動(dòng)畫


實(shí)現(xiàn)組合動(dòng)畫功能主要需要借助AnimatorSet這個(gè)類,這個(gè)類提供了一個(gè)play()方法,如果我們向這個(gè)方法中傳入一個(gè)Animator對(duì)象(ValueAnimator或ObjectAnimator)將會(huì)返回一個(gè)AnimatorSet.Builder的實(shí)例,AnimatorSet.Builder中包括以下四個(gè)方法:

  • after(Animator anim) 將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之后執(zhí)行
  • after(long delay) 將現(xiàn)有動(dòng)畫延遲指定毫秒后執(zhí)行
  • before(Animator anim) 將現(xiàn)有動(dòng)畫插入到傳入的動(dòng)畫之前執(zhí)行
  • with(Animator anim) 將現(xiàn)有動(dòng)畫和傳入的動(dòng)畫同時(shí)執(zhí)行

比如說(shuō)我們想要讓TextView先從屏幕外移動(dòng)進(jìn)屏幕,然后開始旋轉(zhuǎn)360度,旋轉(zhuǎn)的同時(shí)進(jìn)行淡入淡出操作

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textView, "translationX", -700f, 0f);
ObjectAnimator retate = ObjectAnimator.ofFloat(textView, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textView, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(retate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();

效果:


** Animator監(jiān)聽器


在很多時(shí)候,我們希望可以監(jiān)聽到動(dòng)畫的各種事件,比如動(dòng)畫何時(shí)開始,何時(shí)結(jié)束,然后在開始或者結(jié)束的時(shí)候去執(zhí)行一些邏輯處理。這個(gè)功能是完全可以實(shí)現(xiàn)的,Animator類當(dāng)中提供了一個(gè)addListener()方法,這個(gè)方法接收一個(gè)AnimatorListener,我們只需要去實(shí)現(xiàn)這個(gè)AnimatorListener就可以監(jiān)聽動(dòng)畫的各種事件了。

大家已經(jīng)知道,ObjectAnimator是繼承自ValueAnimator的,而ValueAnimator又是繼承自Animator的,因此不管是ValueAnimator還是ObjectAnimator都是可以使用addListener()這個(gè)方法的。另外AnimatorSet也是繼承自Animator的,因此addListener()這個(gè)方法算是個(gè)通用的方法。

上例子:

    public void onAnimationStart(Animator animation) {  
    }     
    @Override  
    public void onAnimationRepeat(Animator animation) {  
    }        
    @Override  
    public void onAnimationEnd(Animator animation) {  
    }       
    @Override  
    public void onAnimationCancel(Animator animation) {  
    }  
});  

可以看到,我們需要實(shí)現(xiàn)接口中的四個(gè)方法,onAnimationStart()方法會(huì)在動(dòng)畫開始的時(shí)候調(diào)用,onAnimationRepeat()方法會(huì)在動(dòng)畫重復(fù)執(zhí)行的時(shí)候調(diào)用,onAnimationEnd()方法會(huì)在動(dòng)畫結(jié)束的時(shí)候調(diào)用,onAnimationCancel()方法會(huì)在動(dòng)畫被取消的時(shí)候調(diào)用。

但是也許很多時(shí)候我們并不想要監(jiān)聽那么多個(gè)事件,可能我只想要監(jiān)聽動(dòng)畫結(jié)束這一個(gè)事件,那么每次都要將四個(gè)接口全部實(shí)現(xiàn)一遍就顯得非常繁瑣。沒關(guān)系,為此Android提供了一個(gè)適配器類,叫作AnimatorListenerAdapter,使用這個(gè)類就可以解決掉實(shí)現(xiàn)接口繁瑣的問題了,如下所示:

anim.addListener(new AnimatorListenerAdapter() {
});

這里我們向addListener()方法中傳入這個(gè)適配器對(duì)象,由于AnimatorListenerAdapter中已經(jīng)將每個(gè)接口都實(shí)現(xiàn)好了,所以這里不用實(shí)現(xiàn)任何一個(gè)方法也不會(huì)報(bào)錯(cuò)。那么如果我想監(jiān)聽動(dòng)畫結(jié)束這個(gè)事件,就只需要單獨(dú)重寫這一個(gè)方法就可以了,如下所示:

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

再來(lái)一個(gè)簡(jiǎn)單的:


ValueAnimator的高級(jí)用法


介紹補(bǔ)間動(dòng)畫缺點(diǎn)的時(shí)候有提到過(guò),補(bǔ)間動(dòng)畫是只能對(duì)View對(duì)象進(jìn)行動(dòng)畫操作的。而屬性動(dòng)畫就不再受這個(gè)限制,它可以對(duì)任意對(duì)象進(jìn)行動(dòng)畫操作。比如說(shuō)我們有一個(gè)自定義的View,在這個(gè)View當(dāng)中有一個(gè)Point對(duì)象用于管理坐標(biāo),然后在onDraw()方法當(dāng)中就是根據(jù)這個(gè)Point對(duì)象的坐標(biāo)值來(lái)進(jìn)行繪制的。也就是說(shuō),如果我們可以對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作,那么整個(gè)自定義View的動(dòng)畫效果就有了。OK,下面我們就來(lái)學(xué)習(xí)一下如何實(shí)現(xiàn)這樣的效果。

首先先學(xué)習(xí)一下TypeEvaluator的用法

TypeEvaluator
簡(jiǎn)單來(lái)說(shuō),就是告訴動(dòng)畫系統(tǒng)如何從初始值過(guò)度到結(jié)束值。我們?cè)谏厦嫖恼轮袑W(xué)到的ValueAnimator.ofFloat()方法就是實(shí)現(xiàn)了初始值與結(jié)束值之間的平滑過(guò)度,那么這個(gè)平滑過(guò)度是怎么做到的呢?其實(shí)就是系統(tǒng)內(nèi)置了一個(gè)FloatEvaluator,它通過(guò)計(jì)算告知?jiǎng)赢嬒到y(tǒng)如何從初始值過(guò)度到結(jié)束值,我們來(lái)看一下FloatEvaluator的代碼實(shí)現(xiàn):

public class FloatEvaluator implements TypeEvaluator {  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        float startFloat = ((Number) startValue).floatValue();  
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
    }  
}  

可以看到,F(xiàn)loatEvaluator實(shí)現(xiàn)了TypeEvaluator接口,然后重寫evaluate()方法。evaluate()方法當(dāng)中傳入了三個(gè)參數(shù),第一個(gè)參數(shù)fraction非常重要,這個(gè)參數(shù)用于表示動(dòng)畫的完成度的,我們應(yīng)該根據(jù)它來(lái)計(jì)算當(dāng)前動(dòng)畫的值應(yīng)該是多少,第二第三個(gè)參數(shù)分別表示動(dòng)畫的初始值和結(jié)束值。那么上述代碼的邏輯就比較清晰了,用結(jié)束值減去初始值,算出它們之間的差值,然后乘以fraction這個(gè)系數(shù),再加上初始值,那么就得到當(dāng)前動(dòng)畫的值了。

那FloatEvaluator是系統(tǒng)內(nèi)置好的功能,并不需要我們自己去編寫,但介紹它的實(shí)現(xiàn)方法是要為我們后面的功能鋪路的。前面我們使用過(guò)了ValueAnimator的ofFloat()和ofInt()方法,分別用于對(duì)浮點(diǎn)型和整型的數(shù)據(jù)進(jìn)行動(dòng)畫操作的,但實(shí)際上ValueAnimator中還有一個(gè)ofObject()方法,是用于對(duì)任意對(duì)象進(jìn)行動(dòng)畫操作的。但是相比于浮點(diǎn)型或整型數(shù)據(jù),對(duì)象的動(dòng)畫操作明顯要更復(fù)雜一些,因?yàn)橄到y(tǒng)將完全無(wú)法知道如何從初始對(duì)象過(guò)度到結(jié)束對(duì)象,因此這個(gè)時(shí)候我們就需要實(shí)現(xiàn)一個(gè)自己的TypeEvaluator來(lái)告知系統(tǒng)如何進(jìn)行過(guò)度。

先定義一個(gè)Point類,如下所示:

 public class Point {  
  
    private float x;  
  
    private float y;  
  
    public Point(float x, float y) {  
        this.x = x;  
        this.y = y;  
    }  
  
    public float getX() {  
        return x;  
    }  
  
    public float getY() {  
        return y;  
    }  
  
}  

x和y兩個(gè)變量用于記錄坐標(biāo)的位置,并提供了構(gòu)造方法來(lái)設(shè)置坐標(biāo),以及get方法來(lái)獲取坐標(biāo)。接下來(lái)定義PointEvaluator,如下所示:

public class PointEvaluator implements TypeEvaluator{  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        Point startPoint = (Point) startValue;  
        Point endPoint = (Point) endValue;  
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
        Point point = new Point(x, y);  
        return point;  
    }  
  
}  

可以看到,PointEvaluator同樣實(shí)現(xiàn)了TypeEvaluator接口并重寫了evaluate()方法。其實(shí)evaluate()方法中的邏輯還是非常簡(jiǎn)單的,先是將startValue和endValue強(qiáng)轉(zhuǎn)成Point對(duì)象,然后同樣根據(jù)fraction來(lái)計(jì)算當(dāng)前動(dòng)畫的x和y的值,最后組裝到一個(gè)新的Point對(duì)象當(dāng)中并返回。

這樣我們就將PointEvaluator編寫完成了,接下來(lái)我們就可以非常輕松地對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作了,比如說(shuō)我們有兩個(gè)Point對(duì)象,現(xiàn)在需要將Point1通過(guò)動(dòng)畫平滑過(guò)度到Point2,就可以這樣寫:

Point point1 = new Point(0, 0);  
Point point2 = new Point(300, 300);  
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
anim.setDuration(5000);  
anim.start();  

這里我們先是new出了兩個(gè)Point對(duì)象,并在構(gòu)造函數(shù)中分別設(shè)置了它們的坐標(biāo)點(diǎn)。然后調(diào)用ValueAnimator的ofObject()方法來(lái)構(gòu)建ValueAnimator的實(shí)例,這里需要注意的是,ofObject()方法要求多傳入一個(gè)TypeEvaluator參數(shù),這里我們只需要傳入剛才定義好的PointEvaluator的實(shí)例就可以了。

好的,這就是自定義TypeEvaluator的全部用法,掌握了這些知識(shí)之后,我們就可以來(lái)嘗試一下如何通過(guò)對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作,從而實(shí)現(xiàn)整個(gè)自定義View的動(dòng)畫效果。

MyAnimView代碼如下所示:

    public class MyAnimView extends View {  

    public static final float RADIUS = 50f;  
  
    private Point currentPoint;  
  
    private Paint mPaint;  
  
    public MyAnimView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setColor(Color.BLUE);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        if (currentPoint == null) {  
            currentPoint = new Point(RADIUS, RADIUS);  
            drawCircle(canvas);  
            startAnimation();  
        } else {  
            drawCircle(canvas);  
        }  
    }  
  
    private void drawCircle(Canvas canvas) {  
        float x = currentPoint.getX();  
        float y = currentPoint.getY();  
        canvas.drawCircle(x, y, RADIUS, mPaint);  
    }  
  
    private void startAnimation() {  
        Point startPoint = new Point(RADIUS, RADIUS);  
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);  
        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
            @Override  
            public void onAnimationUpdate(ValueAnimator animation) {  
                currentPoint = (Point) animation.getAnimatedValue();  
                invalidate();  
            }  
        });  
        anim.setDuration(5000);  
        anim.start();  
    }  
  
} 

首先在自定義View的構(gòu)造方法當(dāng)中初始化了一個(gè)Paint對(duì)象作為畫筆,并將畫筆顏色設(shè)置為藍(lán)色,接著在onDraw()方法當(dāng)中進(jìn)行繪制。這里我們繪制的邏輯是由currentPoint這個(gè)對(duì)象控制的,如果currentPoint對(duì)象不等于空,那么就調(diào)用drawCircle()方法在currentPoint的坐標(biāo)位置畫出一個(gè)半徑為50的圓,如果currentPoint對(duì)象是空,那么就調(diào)用startAnimation()方法來(lái)啟動(dòng)動(dòng)畫。

那么我們來(lái)觀察一下startAnimation()方法中的代碼,其實(shí)大家應(yīng)該很熟悉了,就是對(duì)Point對(duì)象進(jìn)行了一個(gè)動(dòng)畫操作而已。這里我們定義了一個(gè)startPoint和一個(gè)endPoint,坐標(biāo)分別是View的左上角和右下角,并將動(dòng)畫的時(shí)長(zhǎng)設(shè)為5秒。然后有一點(diǎn)需要大家注意的,就是我們通過(guò)監(jiān)聽器對(duì)動(dòng)畫的過(guò)程進(jìn)行了監(jiān)聽,每當(dāng)Point值有改變的時(shí)候都會(huì)回調(diào)onAnimationUpdate()方法。在這個(gè)方法當(dāng)中,我們對(duì)currentPoint對(duì)象進(jìn)行了重新賦值,并調(diào)用了invalidate()方法,這樣的話onDraw()方法就會(huì)重新調(diào)用,并且由于currentPoint對(duì)象的坐標(biāo)已經(jīng)改變了,那么繪制的位置也會(huì)改變,于是一個(gè)平移的動(dòng)畫效果也就實(shí)現(xiàn)了。

布局文件:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    >  
  
    <com.example.tony.myapplication.MyAnimView  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" />  
  
</RelativeLayout>  

偉大的效果圖:


ObjectAnimator的高級(jí)用法


屬性動(dòng)畫就不會(huì)受條條框框的限制,它的擴(kuò)展性非常強(qiáng),例如對(duì)于動(dòng)態(tài)改變View的顏色這種功能是完全可是勝任的,那么下面我們就來(lái)學(xué)習(xí)一下如何實(shí)現(xiàn)這樣的效果。

大家應(yīng)該都還記得,ObjectAnimator內(nèi)部的工作機(jī)制是通過(guò)尋找特定屬性的get和set方法,然后通過(guò)方法不斷地對(duì)值進(jìn)行改變,從而實(shí)現(xiàn)動(dòng)畫效果的。因此我們就需要在MyAnimView中定義一個(gè)color屬性,并提供它的get和set方法。這里我們可以將color屬性設(shè)置為字符串類型,使用#RRGGBB這種格式來(lái)表示顏色值,代碼如下所示:

public class MyAnimView extends View {  
  
    ...  
  
    private String color;  
  
    public String getColor() {  
        return color;  
    }  
  
    public void setColor(String color) {  
        this.color = color;  
        mPaint.setColor(Color.parseColor(color));  
        invalidate();  
    }  
  
    ...  
  
}  

注意在setColor()方法當(dāng)中,我們編寫了一個(gè)非常簡(jiǎn)單的邏輯,就是將畫筆的顏色設(shè)置成方法參數(shù)傳入的顏色,然后調(diào)用了invalidate()方法。這段代碼雖然只有三行,但是卻執(zhí)行了一個(gè)非常核心的功能,就是在改變了畫筆顏色之后立即刷新視圖,然后onDraw()方法就會(huì)調(diào)用。在onDraw()方法當(dāng)中會(huì)根據(jù)當(dāng)前畫筆的顏色來(lái)進(jìn)行繪制,這樣顏色也就會(huì)動(dòng)態(tài)進(jìn)行改變了。

那么接下來(lái)的問題就是怎樣讓setColor()方法得到調(diào)用了,毫無(wú)疑問,當(dāng)然是要借助ObjectAnimator類,但是在使用ObjectAnimator之前我們還要完成一個(gè)非常重要的工作,就是編寫一個(gè)用于告知系統(tǒng)如何進(jìn)行顏色過(guò)度的TypeEvaluator。創(chuàng)建ColorEvaluator并實(shí)現(xiàn)TypeEvaluator接口,代碼如下所示:

public class ColorEvaluator implements TypeEvaluator {  
  
    private int mCurrentRed = -1;  
  
    private int mCurrentGreen = -1;  
  
    private int mCurrentBlue = -1;  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        String startColor = (String) startValue;  
        String endColor = (String) endValue;  
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);  
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);  
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);  
        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);  
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);  
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);  
        // 初始化顏色的值  
        if (mCurrentRed == -1) {  
            mCurrentRed = startRed;  
        }  
        if (mCurrentGreen == -1) {  
            mCurrentGreen = startGreen;  
        }  
        if (mCurrentBlue == -1) {  
            mCurrentBlue = startBlue;  
        }  
        // 計(jì)算初始顏色和結(jié)束顏色之間的差值  
        int redDiff = Math.abs(startRed - endRed);  
        int greenDiff = Math.abs(startGreen - endGreen);  
        int blueDiff = Math.abs(startBlue - endBlue);  
        int colorDiff = redDiff + greenDiff + blueDiff;  
        if (mCurrentRed != endRed) {  
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,  
                    fraction);  
        } else if (mCurrentGreen != endGreen) {  
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,  
                    redDiff, fraction);  
        } else if (mCurrentBlue != endBlue) {  
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,  
                    redDiff + greenDiff, fraction);  
        }  
        // 將計(jì)算出的當(dāng)前顏色的值組裝返回  
        String currentColor = "#" + getHexString(mCurrentRed)  
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);  
        return currentColor;  
    }  
  
    /** 
     * 根據(jù)fraction值來(lái)計(jì)算當(dāng)前的顏色。 
     */  
    private int getCurrentColor(int startColor, int endColor, int colorDiff,  
            int offset, float fraction) {  
        int currentColor;  
        if (startColor > endColor) {  
            currentColor = (int) (startColor - (fraction * colorDiff - offset));  
            if (currentColor < endColor) {  
                currentColor = endColor;  
            }  
        } else {  
            currentColor = (int) (startColor + (fraction * colorDiff - offset));  
            if (currentColor > endColor) {  
                currentColor = endColor;  
            }  
        }  
        return currentColor;  
    }  
      
    /** 
     * 將10進(jìn)制顏色值轉(zhuǎn)換成16進(jìn)制。 
     */  
    private String getHexString(int value) {  
        String hexString = Integer.toHexString(value);  
        if (hexString.length() == 1) {  
            hexString = "0" + hexString;  
        }  
        return hexString;  
    }  
  
}  

沒錯(cuò),屬性動(dòng)畫的高級(jí)用法中最有技術(shù)含量的也就是如何編寫出一個(gè)合適的TypeEvaluator。好在剛才我們已經(jīng)編寫了一個(gè)PointEvaluator,對(duì)它的基本工作原理已經(jīng)有了了解,那么這里我們主要學(xué)習(xí)一下ColorEvaluator的邏輯流程吧。

首先在evaluate()方法當(dāng)中獲取到顏色的初始值和結(jié)束值,并通過(guò)字符串截取的方式將顏色分為RGB三個(gè)部分,并將RGB的值轉(zhuǎn)換成十進(jìn)制數(shù)字,那么每個(gè)顏色的取值范圍就是0-255。接下來(lái)計(jì)算一下初始顏色值到結(jié)束顏色值之間的差值,這個(gè)差值很重要,決定著顏色變化的快慢,如果初始顏色值和結(jié)束顏色值很相近,那么顏色變化就會(huì)比較緩慢,而如果顏色值相差很大,比如說(shuō)從黑到白,那么就要經(jīng)歷255*3這個(gè)幅度的顏色過(guò)度,變化就會(huì)非常快。

那么控制顏色變化的速度是通過(guò)getCurrentColor()這個(gè)方法來(lái)實(shí)現(xiàn)的,這個(gè)方法會(huì)根據(jù)當(dāng)前的fraction值來(lái)計(jì)算目前應(yīng)該過(guò)度到什么顏色,并且這里會(huì)根據(jù)初始和結(jié)束的顏色差值來(lái)控制變化速度,最終將計(jì)算出的顏色進(jìn)行返回。

最后,由于我們計(jì)算出的顏色是十進(jìn)制數(shù)字,這里還需要調(diào)用一下getHexString()方法把它們轉(zhuǎn)換成十六進(jìn)制字符串,再將RGB顏色拼裝起來(lái)之后作為最終的結(jié)果返回。

好了,ColorEvaluator寫完之后我們就把最復(fù)雜的工作完成了,剩下的就是一些簡(jiǎn)單調(diào)用的問題了,比如說(shuō)我們想要實(shí)現(xiàn)從藍(lán)色到紅色的動(dòng)畫過(guò)度,歷時(shí)5秒,就可以這樣寫:

ObjectAnimator anim = ObjectAnimator.ofObject(myAnimView, "color", new ColorEvaluator(),   
    "#0000FF", "#FF0000");  
anim.setDuration(5000);  
anim.start();  

接下來(lái)我們需要將上面一段代碼移到MyAnimView類當(dāng)中,讓它和剛才的Point移動(dòng)動(dòng)畫可以結(jié)合到一起播放,這就要借助我們?cè)谏掀恼庐?dāng)中學(xué)到的組合動(dòng)畫的技術(shù)了。修改MyAnimView中的代碼,如下所示:

public class MyAnimView extends View {  
  
    ...  
  
    private void startAnimation() {  
        Point startPoint = new Point(RADIUS, RADIUS);  
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);  
        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
            @Override  
            public void onAnimationUpdate(ValueAnimator animation) {  
                currentPoint = (Point) animation.getAnimatedValue();  
                invalidate();  
            }  
        });  
        ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),   
                "#0000FF", "#FF0000");  
        AnimatorSet animSet = new AnimatorSet();  
        animSet.play(anim).with(anim2);  
        animSet.setDuration(5000);  
        animSet.start();  
    }  
  
}  

可以看到,我們并沒有改動(dòng)太多的代碼,重點(diǎn)只是修改了startAnimation()方法中的部分內(nèi)容。這里先是將顏色過(guò)度的代碼邏輯移動(dòng)到了startAnimation()方法當(dāng)中,注意由于這段代碼本身就是在MyAnimView當(dāng)中執(zhí)行的,因此ObjectAnimator.ofObject()的第一個(gè)參數(shù)直接傳this就可以了。接著我們又創(chuàng)建了一個(gè)AnimatorSet,并把兩個(gè)動(dòng)畫設(shè)置成同時(shí)播放,動(dòng)畫時(shí)長(zhǎng)為五秒,最后啟動(dòng)動(dòng)畫。現(xiàn)在重新運(yùn)行一下代碼,效果如下圖所示:


更多高級(jí)用法

繼續(xù)深入了解:http://blog.csdn.net/guolin_blog/article/details/44171115
博客中的效果圖:




最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,656評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評(píng)論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,855評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,254評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,473評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,014評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,833評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,016評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,273評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評(píng)論 1 288
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,730評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,006評(píng)論 2 374

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

  • 從凱哥Blog copy 過(guò)來(lái) HenCoder Android 自定義 View 1-7:屬性動(dòng)畫 Proper...
    青果果閱讀 468評(píng)論 0 0
  • “那我呢?”楊戩半開玩笑半是吃醋道。雖然平常是寸心照顧陪伴孩子多些,可是他這個(gè)當(dāng)?shù)囊膊诲e(cuò)啊,國(guó)慶節(jié)五一勞動(dòng)節(jié)不拘...
    木勺的大刀閱讀 250評(píng)論 0 0
  • 今天下班以后,就馬上打了一桶水然后用熱水器燒著,然后就是拿出手機(jī)打開百度,就這樣差不多一個(gè)小時(shí)過(guò)去,這時(shí)候又想到如...
    張志鵬_7bba閱讀 274評(píng)論 0 0
  • 年輕時(shí),我知道那是我的錯(cuò),但是回不去了 天氣熱烘烘的,雨就是下不來(lái)。在這個(gè)小地方,再熱的天恐怕也只能干熱著,沒有空...
    侃侃坤sir閱讀 199評(píng)論 0 0
  • 抱歉,我已不再喝茶 轆轆饑腸,沒有過(guò)多的脂肪 抱歉,我已不再抽煙 傷心傷肺,醫(yī)院已排不上號(hào) 抱歉,我已不再喝酒 酒...
    馬流云閱讀 228評(píng)論 0 2