功能強大的屬性動畫(property animation)
最近在學習有關(guān)自定義View的內(nèi)容肝箱,在Github上看到好多開源的View控件锨推,如果涉及動畫距辆,基本上都使用的是屬性動畫芜果,真心覺得屬性動畫比以前的補間動畫強大太多了,也學習到了使用屬性動畫自定義View的方便和強大鹉戚。所以想記錄一下在自定義View時鲜戒,使用屬性動畫的幾個方面。
屬性動畫的強大之處在于可以對任意對象的任意屬性增加動畫效果抹凳,并且可以自定義值的類型和變化過程(TypeEvaluator)和過渡速度(Interpolator)。
這篇文章先來看看ValueAnimator的使用方法伦腐。
這篇文章也發(fā)步在我的博客
一.ValueAnimator
ValueAnimator是屬性動畫的核心類赢底,最常用的ObjectAnimator(下篇文章會講到)就是它的子類。此類只是以特定的方式(可以自定義)對值進行不斷的修改柏蘑,已達到某種想要的過渡效果幸冻。它提供設(shè)置播放次數(shù)、動畫間隔咳焚、重復模式洽损、開始動畫以及設(shè)置動畫監(jiān)聽器的方法。
看一個最簡單的例子吧革半。
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
animator.setDuration(3000);
animator.setInterpolator(new LinearInterpolator());
animator.start();
以上代碼先使用ofFloat方法傳遞0碑定,1參數(shù)初始化了一個ValueAnimator對象,接著設(shè)置動畫播放的時間又官,設(shè)置變化速率為系統(tǒng)提供的線性變化延刘,最后啟動動畫。
效果是六敬,在3000毫秒內(nèi)動畫float值從0線性增加到1碘赖。
可以添加監(jiān)聽器獲取具體改變的值。
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float value = (Float)animation.getAnimatedValue();
Log.d(TAG, value);
}
});
animator.start();
結(jié)果打印從0到1線性變化的值,并且耗時3000毫秒普泡。如果我們不設(shè)置Interpolator播掷,會調(diào)用默認的Interpolator,先加速增加后減速增加撼班。
二.怎樣使用ValueAnimator自定義View動畫叮趴?
當然,上面的代碼只是對數(shù)字的變化的操作权烧,并沒有涉及到動畫效果眯亦。接下來我們通過在動畫開始(start方法)前設(shè)置監(jiān)聽器來讓自定義View做出相應(yīng)的動畫。
如果想要做出如下圖所示的效果般码,使用ValueAnimator就特別簡單妻率。
這是效果圖:
簡單分析得知,由于效果是一個小球從左邊移動一段距離后板祝,變化的值只有小球圓心的X軸坐標宫静。所以可以利用ValueAnimator產(chǎn)生從開始位置到結(jié)束位置的一系列中間值,在監(jiān)聽器中把每次改變的值設(shè)置給代表小球圓心X軸坐標的變量券时,再通知view重新繪制孤里。我們看看這樣會不會產(chǎn)出想要的動畫效果。
onDraw方法:根據(jù)XPoint(x軸坐標)繪制圓形橘洞。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(XPoint, heightSpecSize / 2, 30, mPaint);
}
對外提供開始動畫的start方法:
創(chuàng)建ValueAnimator對象捌袜,設(shè)置必要的屬性,添加監(jiān)聽器把每次改變的值賦值給xPoint炸枣,并且通知view重繪虏等,最后開始動畫。
public void start() {
final ValueAnimator animator = ValueAnimator.ofFloat(60, 600);
animator.setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.RESTART);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 獲取到動畫每次該變得float值适肠,賦值給xpoint
XPoint = (Float)animation.getAnimatedValue();
// 通知view重繪
invalidate();
}
});
animator.start();
}
以上代碼首先創(chuàng)建一個從60變化到600的ValueAnimator對象霍衫,接著設(shè)置動畫時間為2000毫秒、重播方式為從頭開始播放侯养、重播次數(shù)為無限和速度變化情況為線性變化敦跌,最后增加監(jiān)聽器,獲取每次變化的值賦值給xPoint逛揩,接著很重要的一點柠傍,調(diào)用invalidate()方法通知View重繪,即float值每次改變都需要View重繪息尺。
這樣就很方便的根據(jù)float值的改變携兵,給view增加了動畫的效果。
三.TypeEvaluator
前面的例子搂誉,創(chuàng)建ValueAnimator的時候徐紧,都是使用的ValueAnimator.ofFloat(float, float)方法,這個方法傳遞的參數(shù)為可變參數(shù),可以傳遞多個float值并级。其實創(chuàng)建ValueAnimator也可以使用ofInt等方法拂檩,這里有一個非常重要的方法:ValueAnimator.ofObject(TypeEvaluator, Object...),此方法和其他方法不同之處在于第一個參數(shù)TypeEvaluator,此處需要使用系統(tǒng)已經(jīng)實現(xiàn)好的或自定義子類,用于設(shè)定自定義類型嘲碧。
在使用ofInt或ofFloat方法時稻励,內(nèi)部其實是使用了FloatEvaluator、FloatArrayEvaluator`愈涩、IntEvaluator望抽、IntArrayEvaluator這些系統(tǒng)已經(jīng)實現(xiàn)好了的TypeEvaluator。我們使用這些方法創(chuàng)建ValueAnimator時就不必自定義類來繼承TypeEvaluator履婉。
下面我們試試自定義一個TypeEvaluator煤篙,實現(xiàn)我們想要的類型。
假如現(xiàn)在需要這個圓形斜著移動毁腿,使用ValueAnimator該怎樣實現(xiàn)辑奈?當然有很多實現(xiàn)方法,比如Path路徑的使用已烤,這里為了演示自定義TypeEvaluator鸠窗,使用自定義TypeEvaluator的方式來實現(xiàn)。
這是效果圖:
分析得知胯究,和上面的橫向移動不同稍计,斜著移動改變的是圓心的坐標,包括x軸和y軸坐標唐片,我們可以自定義或使用系統(tǒng)提供的Point來表示一個二維坐標下的點丙猬,實現(xiàn)TypeEvaluator接口,根據(jù)動畫完成的比例费韭,返回一個具體代表點坐標的Point對象。
自定義類PointEvaluator實現(xiàn)TypeEvaluator接口庭瑰。
class PointEvaluator implements TypeEvaluator{
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
return new Point(x, y);
}
}
這里需要實現(xiàn)evaluate方法星持,根據(jù)動畫完成的百分比返回對應(yīng)的值。其中fraction參數(shù)和下文的TimeInterpolator接口方法返回的值有關(guān)弹灭,可以理解為動畫執(zhí)行的完成程度督暂,比如動畫總時間為3000毫秒,現(xiàn)在執(zhí)行了1000毫秒穷吮,那么此刻傳遞進來的fraction參數(shù)值可以為三分之一逻翁。
在onDraw方法中依然還是簡單的繪制一個圓形,此圓的圓心坐標是成員變量mPoint的x,y值捡鱼。
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mPoint.x, mPoint.y, 30, mPaint);
}
最后提供start方法開始動畫八回。
public void start() {
final ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),
new Point(30, 30), new Point(600, 600));
animator.setDuration(2000);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setInterpolator(new LinearInterpolator());
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPoint = (Point)animation.getAnimatedValue();
invalidate();
}
});
animator.start();
}
和上邊的代碼類似,只是ValueAnimotor操作的值從float改變成了Point(可以自定義類型)。我們使用ValueAnimator.ofObject方法創(chuàng)建了一個ValueAnimator對象缠诅,并且傳遞給了兩個Point對象溶浴,表示需要改變的Point值得范圍,接著設(shè)置動畫的執(zhí)時間為2000毫秒管引、重播方式為從頭開始播放士败、重播次數(shù)為無限和速度變化情況為線性變化,最后增加監(jiān)聽器褥伴,獲取每次變化的值賦值給mPoint 谅将,接著通知view重繪。
這個例子重慢,我們可以知道ValueAnimotor操作的值的類型是任意的饥臂,可以由我們來自定義,只要自定義類實現(xiàn)TypeEvaluator伤锚,并且實現(xiàn)此接口的唯一一個方法evaluate即可擅笔。
四.TimeInterpolator
TimeInterpolator表示動畫的速率,上邊代碼中我們就設(shè)置了動畫速率屯援,只不過使用的是API中已經(jīng)實現(xiàn)好了的LinearInterpolator猛们。
查詢API知道TimeInterpolator接口有很多已知的實現(xiàn)類,比如
AccelerateDecelerateInterpolator表示先加速后減速狞洋,
AccelerateInterpolator表示一直加速弯淘,
DecelerateInterpolator表示一直加速等。
BounceInterpolator可以模擬物理規(guī)律吉懊,實現(xiàn)反彈的效果
如果不設(shè)置setInterpolator,那么默認使用AccelerateDecelerateInterpolator庐橙。
在自定義TimeInterpolator之前,我們先看看API中提供的實現(xiàn)的例子:LinearInterpolator,AccelerateInterpolator借嗽。
TimeInterpolator接口态鳖,只有一個方法:getInterpolation(float input) ,此方法接收一個float類型的input值,此值的變化范圍為0~1恶导,并且根據(jù)動畫運行的時間浆竭,均勻增加,和TypeEvaluator接口方法中的參數(shù)fraction很像惨寿,fraction也是根據(jù)動畫運行的時間邦泄,均勻增加。
注意:getInterpolation函數(shù)的返回值傳遞給了fraction裂垦,最好讓此函數(shù)返回從0~1的值顺囊,并且遞增,這樣意義比較明確:代表動畫完成的程度蕉拢。自定義TypeEvaluator中也會比較好處理特碳。
LinearInterpolator
源碼:
public float getInterpolation(float input) {
return input;
}
從源碼中诚亚,可以看到getInterpolation的邏輯簡單到不能再簡單,直接返回input测萎,因為input本身表示的就是均勻增加的亡电。
AccelerateInterpolator源碼:
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
構(gòu)造函數(shù)接收一個mFactor表示加速的倍數(shù),接收1.0f以上的數(shù)硅瞧,mDoubleFactor = 2 * mFactor份乒。
在getInterpolation方法中,判斷mFactor如果等于1.0f腕唧,直接返回input * input(默認或辖,二次函數(shù)增長),否則返回input的mDoubleFactor次方(mDoubleFactor次函數(shù)增長)枣接。
看來要想實現(xiàn)一個自定義的TimeInterpolator颂暇,得要有一些必要的數(shù)學修養(yǎng)了。沒辦法但惶,數(shù)學沒有那么好耳鸯,只能實現(xiàn)一個簡單的TimeInterpolator,演示自定義TimeInterpolator的步驟膀曾。
下面我們實現(xiàn)一個以10給底數(shù)的負指數(shù)函數(shù)減速的例子:
class LgDecelerateInterpolator implements TimeInterpolator {
private float background;
public LgDecelerateInterpolator() {
background = 10;
}
@Override
public float getInterpolation(float input) {
return (1 - (float) Math.pow(background, -input));
}
}
然后在設(shè)置animator.setInterpolator(new LgDecelerateInterpolator());,就可以使用了县爬。
成員變量background表示底數(shù),在構(gòu)造方法中初始化為10添谊,因為是減速财喳,所以用到了負指數(shù),得到的值從1變化到0斩狱,所以再用1減去這個結(jié)果值耳高,就得到了最終的結(jié)果。
五.AnimatorSet
AnimatorSet表示動畫的集合所踊,可以把幾個動畫一起播放泌枪,或按次序播放。提供paly秕岛、with工闺、after等方法。
接下來瓣蛀,把以上用到的全部結(jié)合起來,播放一個動畫的效果:圓形從View的左上角移動到右下角雷厂,伴隨著顏色的變化惋增,移動速度和顏色變化的速率都由上面自定義的LgDecelerateInterpolator實現(xiàn)。
這是效果圖:
這是完整的代碼:
public class MyView extends View {
private Paint mPaint;
private Point mPoint;
private int mColor;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
initPaint();
}
public MyView(Context context) {
super(context);
initPaint();
}
private void initPaint() {
mPaint = new Paint();
mPaint.setColor(0xFFF00000);
mPaint.setAntiAlias(true); // 抗鋸齒
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(mPoint.x, mPoint.y, 60, mPaint);
}
public void start() {
final ValueAnimator animator = ValueAnimator.ofObject(new PointEvaluator(),
new Point(60, 60), new Point(990, 1050));
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
final ValueAnimator animator1 = ValueAnimator.ofArgb(0xFFF00000,0xFFFFFF00);
animator1.setRepeatCount(ValueAnimator.INFINITE);
animator1.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mColor = (int) animation.getAnimatedValue();
mPaint.setColor(mColor);
}
});
AnimatorSet animationSet = new AnimatorSet();
animationSet.setDuration(3000);
animationSet.setInterpolator(new LgDecelerateInterpolator());
animationSet.play(animator).with(animator1);
animationSet.start();
}
class PointEvaluator implements TypeEvaluator {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
int x = (int) (startPoint.x + fraction * (endPoint.x - startPoint.x));
int y = (int) (startPoint.y + fraction * (endPoint.y - startPoint.y));
return new Point(x, y);
}
}
class LgDecelerateInterpolator implements TimeInterpolator {
private float background;
public LgDecelerateInterpolator() {
background = 10;
}
@Override
public float getInterpolation(float input) {
return (1 - (float) Math.pow(background, -input));
}
}
}
start方法中創(chuàng)建了兩個ValueAnimator,第一個使用.ofObject方法改鲫,通過傳遞自定義的PointEvaluator,第二個使用API已經(jīng)實現(xiàn)的ofArgb使顏色值變化的動畫屬性诈皿,都添加監(jiān)聽器以實現(xiàn)對成員變量的修改林束,重繪View,最后創(chuàng)建AnimatorSet對兩個動畫進行疊加稽亏,在播放移動動畫的同時播放顏色漸變的動畫壶冒。
下篇文章為屬性動畫ObjectAnimator在自定義View中的使用(計劃),是更為常用的ObjectAnimator的使用截歉。