Android 動(dòng)畫之屬性動(dòng)畫(三)#
Animation Listeners##
前面的兩節(jié)中都或多或少提到了動(dòng)畫監(jiān)聽 Animation Listeners 這個(gè)概念。它用來監(jiān)聽動(dòng)畫的過程,并且提供一些接口讓你實(shí)現(xiàn)一些邏輯。比如說你想在動(dòng)畫開始的時(shí)候進(jìn)行一些操作,就需要為這個(gè)動(dòng)畫設(shè)置一個(gè)監(jiān)聽器,然后實(shí)現(xiàn)你的邏輯,這樣,當(dāng)監(jiān)聽器監(jiān)聽到動(dòng)畫開始,就回去實(shí)現(xiàn)你的邏輯
Animator.AnimatorListener
Animator 類當(dāng)中提供了一個(gè) addListener() 方法,這個(gè)方法接收一個(gè)接口類 AnimatorListener ,獲得該接口后就可以重寫以下四個(gè)方法:
- onAnimationStart() - 當(dāng)動(dòng)畫開始的時(shí)候調(diào)用
- onAnimationEnd() - 當(dāng)動(dòng)畫結(jié)束后調(diào)用
- onAnimationRepeat() - 當(dāng)動(dòng)畫重復(fù)播放時(shí)調(diào)用
- onAnimationCancel() - 當(dāng)動(dòng)畫被取消的時(shí)候調(diào)用,并且在調(diào)用完這個(gè)方法后,會(huì)自動(dòng)調(diào)用 onAnimationEnd() 方法
下面的例子中為每一段代碼實(shí)現(xiàn)了一個(gè)邏輯:彈出對(duì)應(yīng)的文字
anim.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
Toast.makeText(MainActivity.this, "start", Toast.LENGTH_LONG).show();
}
@Override
public void onAnimationEnd(Animator animator) {
Toast.makeText(MainActivity.this, "End", Toast.LENGTH_LONG).show();
}
@Override
public void onAnimationCancel(Animator animator) {
Toast.makeText(MainActivity.this, "Cancel", Toast.LENGTH_LONG).show();
}
@Override
public void onAnimationRepeat(Animator animator) {
Toast.makeText(MainActivity.this, "rapeat", Toast.LENGTH_LONG).show();
}
});
anim.start();
//添加一個(gè)按鈕,按鈕的功能用于cancel動(dòng)畫
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
anim.cancel();
}
});
來看運(yùn)行后效果



按下cancel鍵后


當(dāng)然如果你不想把所有方法都重寫的話,可以使用 AnimatorListenerAdapter 類,如下
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
}
});
這樣的話就可以選擇自己想要重寫方法進(jìn)行重寫
ValueAnimator.AnimatorUpdateListener
Animator 類當(dāng)中提供了一個(gè) addUpdateListener() 方法,這個(gè)方法接收一個(gè)接口類 AnimatorUpdateListener ,獲得該接口后就可以重寫 onAnimationUpdate() 方法,這個(gè)方法為動(dòng)畫提供每一幀的監(jiān)聽,之前在(一)的時(shí)候提到,每一幀其實(shí)是用一個(gè) value 值來控制的,所以通常情況下我們都需要在每一幀監(jiān)聽的時(shí)候獲得這個(gè) value 值,就需要調(diào)用 ValueAnimator.getAnimatedValue() 方法來獲得。
ValueAnimator vanim = ValueAnimator.ofInt(0,10,20);
vanim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
//如果之前的ValueAnimtor指定的是Int的i話,那么返回的Value就是int類型,
也就是返回值類型與你創(chuàng)建的類型一致
int value = (int) valueAnimator.getAnimatedValue();
}
});
Animating Layout Changes to ViewGroups##
屬性動(dòng)畫不僅可以讓你對(duì) view 類進(jìn)行動(dòng)畫操作,同時(shí)它還可以用于 viewgroup 類的變化進(jìn)行動(dòng)畫操作。比如說你有一個(gè) gridLayout ,你需要在里面添加按鈕,那么按鈕加到 gridLayout 是一個(gè)動(dòng)畫的過程,系統(tǒng)會(huì)有一個(gè)默認(rèn)的動(dòng)畫。如果想要改變這個(gè)默認(rèn)的動(dòng)畫效果,就可以借助 LayoutTransition 類來為這個(gè) gridLayout 修改動(dòng)畫效果。使用步驟:
- 新建一個(gè) LayoutTransition
- 對(duì) LayoutTransition 通過調(diào)用 setAnimator ( int transitionType, Animator animator ) 設(shè)置自定義的動(dòng)畫
- 為所需要的 ViewGroup 通過 setLayoutTransition( LayoutTransition ) 添加一個(gè) LayoutTransition
需要注意的是在setAnimator()中的第一個(gè)參數(shù) transitionType 決定了動(dòng)畫的類型,一共有四種:
APPEARING:元素在容器中顯現(xiàn)時(shí)需要?jiǎng)赢嬶@示
CHANGE_APPEARING:由于容器中要顯現(xiàn)一個(gè)新的元素,其它元素的變化需要?jiǎng)赢嬶@示
DISAPPEARING:元素在容器中消失時(shí)需要?jiǎng)赢嬶@示
-
CHANGE_DISAPPEARING:由于容器中某個(gè)元素要消失,其它元素的變化需要?jiǎng)赢嬶@示
代碼: LayoutTransition layoutTransition = new LayoutTransition(); //設(shè)置這個(gè)動(dòng)畫為縮放 ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(null,"scaleX",0f,1f); layoutTransition.setAnimator(LayoutTransition.APPEARING,objectAnimator); gridContainer.setLayoutTransition(layoutTransition); //通過點(diǎn)擊添加按鈕,往gridContainer添加button,這里就涉及到了上面第一種類型的動(dòng)畫:appear add_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Button newButton = new Button(MainActivity.this); newButton.setText(String.valueOf(num++)); gridContainer.addView(newButton, Math.min(1, gridContainer.getChildCount())); } });
可以看到,按鈕出現(xiàn)的時(shí)候是以縮放的形式出現(xiàn)的


更多關(guān)于 LayoutTransition 請(qǐng)查看文檔
https://developer.android.com/reference/android/animation/LayoutTransition.html
Using a TypeEvaluator
在第一篇的時(shí)候我們就有講到 Evaluator 這個(gè)類。請(qǐng)復(fù)習(xí) Android 動(dòng)畫之屬性動(dòng)畫(一)>Evaluator 類。有些時(shí)候你需要返回的動(dòng)畫返回的 value 不是系統(tǒng)默認(rèn)的 Int,F(xiàn)loat 或 Color 類型的,那么你就可以自己自定義這個(gè) Evaluator 來返回你所需要的類型!比如說一個(gè)坐標(biāo)點(diǎn)。這也是 屬性動(dòng)畫 比 補(bǔ)間動(dòng)畫 強(qiáng)大的精髓所在。
那要如何自己實(shí)現(xiàn)呢,首先需要繼承 TypeEvaluator 接口,然后重寫里面的 evaluate() 方法。
//其實(shí)FloatEvaluator也是繼承TypeEvaluator,來實(shí)現(xiàn)的
public class FloatEvaluator implements TypeEvaluator {
//注意看到fraction參數(shù),在第一篇里面提到的參數(shù)
public Object evaluate(float fraction, Object startValue, Object endValue) {
float startFloat = ((Number) startValue).floatValue();
return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
}
}
下面我們來實(shí)現(xiàn)一個(gè)返回一個(gè)坐標(biāo)點(diǎn)的 Evaluator
首先新建一個(gè)點(diǎn)類: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;
}
}
然后我們新建一個(gè) PointEvaluator ,在里面要實(shí)現(xiàn)的是當(dāng)我們傳入一個(gè)初始點(diǎn)和結(jié)束點(diǎn),然后實(shí)現(xiàn)平緩從初始點(diǎn)到結(jié)束點(diǎn)
public class PointEvaluator implements TypeEvaluator {
float x;
float y;
@Override
public Object evaluate(float fraction, Object s, Object e) {
Point startPoint = (Point) s;
Point endPoint = (Point) e;
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;
}
}
然后我們新建一個(gè)view,在view里面有一個(gè)繪制一個(gè)圓,通過對(duì)圓心坐標(biāo)的動(dòng)畫實(shí)現(xiàn)球的動(dòng)畫
代碼的思路就是在左上角畫一個(gè)圓,然后對(duì)圓心坐標(biāo)進(jìn)行進(jìn)行動(dòng)畫,為動(dòng)畫監(jiān)聽,每得到一個(gè)新的圓心坐標(biāo)就重新繪制view,下面代碼略長(zhǎng),耐心看是很容易看懂的
public class myView extends View {
Paint mpaint;
Point CurrentPoint = null;
float radius;
public myView(Context context) {
this(context, null);
}
public myView(Context context, AttributeSet attrs) {
super(context, attrs);
mpaint = new Paint();
mpaint.setStyle(Paint.Style.FILL);
mpaint.setColor(Color.BLUE);
radius = 25f;
}
//CurrentPoint用來記錄當(dāng)前圓心的位置,用來告訴canvas在哪繪制圓,Currentpoint為空時(shí)
則新建一個(gè)坐標(biāo)點(diǎn),然后設(shè)置動(dòng)畫
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (CurrentPoint == null) {
CurrentPoint = new Point(radius, radius);
drawCircle(canvas);
setAnimation();
} else {
drawCircle(canvas);
}
}
//根據(jù)當(dāng)前坐標(biāo)畫圓
private void drawCircle(Canvas canvas) {
canvas.drawCircle(CurrentPoint.getX(), CurrentPoint.getY(), radius, mpaint);
}
//設(shè)置動(dòng)畫。每當(dāng)有新的point返,則通知view重繪
public void setAnimation() {
ValueAnimator valueAnimator = ValueAnimator.ofObject(new PointEvaluator(),
new Point(radius,radius),
new Point(getWidth() - radius, getHeight() - radius));
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
CurrentPoint = (Point) valueAnimator.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(2000);
valueAnimator.start();
}
}
到此我們已經(jīng)完成了自定義的 Evaluator ,請(qǐng)自行運(yùn)行查看效果
Using Interpolators##
前面已經(jīng)提到過 Interpolators 的作用,這里不再重復(fù)。如果你沒有找到適合自己的 Interpolators ,那么就可以繼承 Interpolators 然后重寫 getInterpolation()
方法
以下是系統(tǒng)實(shí)現(xiàn) AccelerateDecelerateInterpolator 和 LinearInterpolator 的算法:
AccelerateDecelerateInterpolator
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
LinearInterpolator
public float getInterpolation(float input) {
return input;
}