一、概述
在Android動畫中,總共有兩種類型的動畫View Animation(視圖動畫)和Property Animator(屬性動畫);其中
View Animation包括Tween Animation(補間動畫)和Frame Animation(逐幀動畫);
Property Animator包括ValueAnimator和ObjectAnimation;
首先,直觀上,他們有如下三點不同:
1、引入時間不同:View Animation是API Level 1就引入的。Property Animation是API Level 11引入的,即Android 3.0才開始有Property Animation相關的API。
2、所在包名不同:View Animation在包android.view.animation中。而Property Animation API在包 android.animation中。
3、動畫類的命名不同:View Animation中動畫類取名都叫XXXXAnimation,而在Property Animator中動畫類的取名則叫XXXXAnimator大家都知道逐幀動畫主要是用來實現動畫的,而補間動畫才能實現控件的漸入漸出、移動、旋轉和縮放的;而Property Animator是在Android 3.0版本才引入的,之前是沒有的。
為什么還要引入Property Animator呢?
1、為什么引入Property Animator(屬性動畫)
我提出一個假設:請問大家,如何利用補間動畫來將一個控件的背景色在一分鐘內從綠色變為紅色?這個效果想必沒辦法僅僅通過改變控件的漸入漸出、移動、旋轉和縮放來實現吧,而這個效果是可以通過Property Animator完美實現的
**這就是第一個原因:Property Animator能實現補間動畫無法實現的功能 **大家都知道,補間動畫和逐幀動畫統稱為View Animation,也就是說這兩個動畫只能對派生自View的控件實例起作用;而Property Animator則不同,從名字中可以看出屬性動畫,應該是作用于控件屬性的!正因為屬性動畫能夠只針對控件的某一個屬性來做動畫,所以也就造就了他能單獨改變控件的某一個屬性的值!比如顏色!這就是Property Animator能實現補間動畫無法實現的功能的最重要原因。
**我們得到了第二點不同:View Animation僅能對指定的控件做動畫,而Property Animator是通過改變控件某一屬性值來做動畫的。
**假設我們將一個按鈕從左上角利用補間動畫將其移動到右下角,在移動過程中和移動后,這個按鈕都是不會響應點擊事件的。這是為什么呢?因為補間動畫僅僅轉變的是控件的顯示位置而已,并沒有改變控件本身的值。View Animation的動畫實現是通過其Parent View實現的,在View被drawn時Parents View改變它的繪制參數,這樣雖然View的大小或旋轉角度等改變了,但View的實際屬性沒變,所以有效區域還是應用動畫之前的區域;我們看到的效果僅僅是系統作用在按鈕上的顯示效果,利用動畫把按鈕從原來的位置移到了右下角,但按鈕內部的任何值是沒有變化的,所以按鈕所捕捉的點擊區域仍是原來的點擊區域。(下面會舉例來說明這個問題)
這就得到了第三點不同:補間動畫雖能對控件做動畫,但并沒有改變控件內部的屬性值。而Property Animator則是恰恰相反,Property Animator是通過改變控件內部的屬性值來達到動畫效果的
二、ValueAnimator簡單使用
我們前面講了Property Animator包括ValueAnimator和ObjectAnimator;這篇文章就主要來看看ValueAnimator的使用方法吧。
我覺得谷歌那幫老頭是最會起名字的人,單從命名上,就能看出來這個東東的含義。ValueAnimator從名字可以看出,這個Animation是針對值的!ValueAnimator不會對控件做任何操作,我們可以給它設定從哪個值運動到哪個值,通過監聽這些值的漸變過程來自己操作控件。它會自己計算動畫的過程,然后我們需要監聽它的動畫過程來自己操作控件。
- 第一步:創建ValueAnimator實例
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.setDuration(1000);
animator.start();
- 在這里我們利用ValueAnimator.ofInt創建了一個值從0到400的動畫,動畫時長是1s,然后讓動畫開始。從這段代碼中可以看出,ValueAnimator沒有跟任何的控件相關聯,那也正好說明ValueAnimator只是對值做動畫運算,而不是針對控件的,我們需要監聽ValueAnimator的動畫過程來自己對控件做操作。 第二步:添加監聽
上面的三行代碼,我們已經實現了動畫,下面我們就添加監聽:
ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
Log.d("qijian","curValue:"+curValue);
}
});
animator.start();
這就是ValueAnimator的功能:ValueAnimator對指定值區間做動畫運算,我們通過對運算過程做監聽來自己操作控件。
總而言之就是兩點:
1. ValueAnimator只負責對指定的數字區間進行動畫運算
2. 我們需要對運算過程進行監聽,然后自己對控件做動畫操作
三、插值器Interpolator
插值器的意義其實就相當于物理公式中的加速度參數,所以這也就是它也叫加速器的原因。 如何自定義插值器:
public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
}
public interface Interpolator extends TimeInterpolator {
}
- getInterpolation(float input):
參數input:input參數是一個float類型,它取值范圍是0到1,表示當前動畫的進度,取0時表示動畫剛開始,取1時表示動畫結束,取0.5時表示動畫中間的位置,其它類推。
返回值:表示當前實際想要顯示的進度。取值可以超過1也可以小于0,超過1表示已經超過目標值,小于0表示小于開始位置。
對于input參數,它表示的是當前動畫的進度,勻速增加的。動畫的進度就是動畫在時間上的進度,與我們的任何設置無關,隨著時間的增長,動畫的進度自然的增加,從0到1;input參數相當于時間的概念,通過setDuration()指定了動畫的時長,在這個時間范圍內,動畫進度肯定是一點點增加的;就相當于播放一首歌,這首歌的進度是從0到1是一樣的。 而返回值則表示動畫的數值進度,它的對應的數值范圍是我們通過ofInt(),ofFloat()來指定的,這個返回值就表示當前時間所對應的數值的進度。
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
**input參數與任何我們設定的值沒關系,只與時間有關,隨著時間的增長,動畫的進度也自然的增加,input參數就代表了當前動畫的進度。而返回值則表示動畫的當前數值進度 **
public class LinearInterpolator implements Interpolator {
…………
public float getInterpolation(float input) {
return 1-input;
}
}
在getInterpolation函數中,我們將進度反轉過來,當傳0的時候,我們讓它數值進度在完成的位置,當完成的時候,我們讓它在開始的位置
四、ObjectAnimator
ObjectAnimator是派生自ValueAnimator的,所以ValueAnimator中所能使用的方法,在ObjectAnimator中都可以正常使用。ObjectAnimator重寫了幾個方法,比如ofInt(),ofFloat()等。利用ObjectAnimator重寫的ofFloat方法如何實現一個動畫:(改變透明度)
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"alpha",1,0,1);
animator.setDuration(2000);
animator.start();
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
- 第一個參數用于指定這個動畫要操作的是哪個控件
- 第二個參數用于指定這個動畫要操作這個控件的哪個屬性
- 第三個參數是可變長參數,這個就跟ValueAnimator中的可變長參數的意義一樣了,就是指這個屬性值是從哪變到哪。像我們上面的代碼中指定的就是將textview的alpha屬性從0變到1再變到0;
TextView控件有rotation這個屬性嗎?沒有,不光TextView沒有,連它的父類View中也沒有這個屬性。那它是怎么來改變這個值的呢?其實,ObjectAnimator做動畫,并不是根據控件xml中的屬性來改變的,而是通過指定屬性所對應的set方法來改變的。比如,我們上面指定的改變rotation的屬性值,ObjectAnimator在做動畫時就會到指定控件(TextView)中去找對應的setRotation()方法來改變控件中對應的值。同樣的道理,當我們在最開始的示例代碼中,指定改變”alpha”屬性值的時候,ObjectAnimator也會到TextView中去找對應的setAlpha()方法。那TextView中都有這些方法嗎,有的,這些方法都是從View中繼承過來的,在View中有關動畫,總共有下面幾組set方法:
public void setAlpha(float alpha)
//2、旋轉度數:rotation、rotationX、rotationY
public void setRotation(float rotation)
public void setRotationX(float rotationX)
public void setRotationY(float rotationY)
//3、平移:translationX、translationY
public void setTranslationX(float translationX)
public void setTranslationY(float translationY)
//縮放:scaleX、scaleY
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)
依據這點,自定義動畫屬性:
public class MyPointView extends View {
private Point mPoint = new Point(100);
public MyPointView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
if (mPoint != null){
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300,300,mPoint.getRadius(),paint);
}
super.onDraw(canvas);
}
//第一點,這個set函數所對應的屬性應該是pointRadius或者PointRadius。前面我們已經講了第一個字母大小寫無所謂,后面的字母必須保持與set函數完全一致。
//第二點,在setPointRadius中,先將當前動畫傳過來的值保存到mPoint中,做為當前圓形的半徑。然后強制界面刷新
void setPointRadius(int radius){
mPoint.setRadius(radius);
invalidate();
}
}
private void doPointViewAnimation(){
ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius", 0, 300, 100);
animator.setDuration(2000);
animator.start();
}
前面我們都是定義多個值,即至少兩個值之間的變化,那如果我們只定義一個值呢,如下面的方式:(同樣以MyPointView為例)
ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);
animator.setDuration(2000);
animator.start();
僅且僅當我們只給動畫設置一個值時,程序才會調用屬性對應的get函數來得到動畫初始值。如果動畫沒有初始值,那么就會使用系統默認值。比如ofInt()中使用的參數類型是int類型的,而系統的Int值的默認值是0,所以動畫就會從0運動到100;也就是系統雖然在找到不到屬性對應的get函數時,會給出警告,但同時會用系統默認值做為動畫初始值。
如果通過給自定義控件MyPointView設置了get函數,那么將會以get函數的返回值做為初始值。
ArgbEvaluator
public class ArgbEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24);
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24);
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
}
根據View setBackGroundColor()
方法可以自定義條用屬性動畫。
ObjectAnimator animator = ObjectAnimator.ofInt(tv, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
animator.setDuration(8000);
animator.setEvaluator(new ArgbEvaluator());
animator.start();