參考于:http://blog.csdn.net/guolin_blog/article/details/43536355
動(dòng)畫簡(jiǎn)介
android提供大致三種動(dòng)畫類型:View Animation, Drawable Animation 、Property Animation 。
- View Animation:
- 一般只能修改組件(View Object)的部分屬性,比如:scaling(大小)和rotation(旋轉(zhuǎn)),但是無(wú)法修改組件的背景顏色,支持簡(jiǎn)單的縮放、平移、旋轉(zhuǎn)、透明度基本的動(dòng)畫,且有一定的局限性,但是當(dāng)View Animation能方便快速地解決需求時(shí),選擇它也是不錯(cuò)的選擇。
- 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
博客中的效果圖: