【轉載】 android 屬性動畫詳解
動畫綜述
Google大大對動畫的總述如下:
Animations can add visual cues that notify users about what's going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.
沒錯,放上原文我只是裝個逼,~
簡單來說,動畫就兩個作用:
- 新增可視提示,通知我們這個APP中正在發生的事情。比如使用者介面發生變化時,有新的內容載入或某些操作變為可用。
- 提供高逼格的外觀(裝逼利器)
動畫的分類如下:
屬性動畫(Property Animation)
概述
- 檢視動畫的缺陷:
- 物件的侷限性:僅限於View
- 只改變了View的視覺效果,而沒有改變View的屬性
- 動畫效果單一
- 屬性動畫的特點:
- 作用物件:任意物件,甚至沒物件也可以
- 作用方式:改變物件的屬性
- 動畫效果:按需自定義,不再侷限於上述4種基本變換
-
繼承關係
繼承關係
Java類名 | XML關鍵字 | 說明 |
---|---|---|
ValueAnimator |
<animator> 放置在res/animator/目錄下 |
在一個特定的時間裡執行一個動畫 |
TimeAnimator | 無 | 時序監聽回撥工具 |
ObjectAnimator |
<objectAnimator> 放置在res/animator/目錄下 |
一個物件的一個屬性動畫 |
AnimatorSet |
<set> 放置在res/animator/目錄下 |
動畫集合 |
工作原理
指定時間內,修改屬性(物件中對應的欄位)的值,以此實現該物件在屬性
為了更好的理解,我們舉一個栗子:
圖1.linear animation
圖1中我們搞出了一個假象的物件,動畫作用於這個物件(實際可以說是物件的x屬性,也即物件的水平位置),動畫持續40ms,移動距離40px。每10ms(預設重新整理速率),物件水平移動10px。40ms後,動畫結束,物體停止在x=40處。這是一個典型的設定了linearInterpolator(勻速插值器)的動畫。
為了更好的瞭解屬性動畫的工作原理,下面我們來看一看屬性動畫的元件是怎麼計算上面例子的動畫的。
圖2.動畫計算過程
其邏輯可以總結如下:
- 為 ValueAnimator 設定動畫的時長,以及對應屬性的始 & 末值
- 設定屬性在 始 & 末值 間的變化邏輯
- TimeInterpolator實現類:插值器-描述動畫的變化速率
- TypeEvaluator實現類:估值器-描述 屬性值 變化的具體數值
- 根據2中的邏輯更新當前值
- 獲取3中更新的 值 ,修改 目標屬性值
- 重新整理檢視。
- 重複4-5,直到 屬性值 == 末值
下面給出動畫工作的關鍵類
Java類 | 說明 |
---|---|
ValueAnimator | 動畫執行類;核心 |
ObjectAnimator | 動畫執行類 |
TimeInterpolator | 時間插值(插值器介面),控制動畫變化率 |
TypeEvaluator | 型別估值(估值器介面),設定屬性值計算方式,根據屬性的 始 & 末值 和 插值 一起計算出當前時間的屬性值 |
AnimatorSet | 動畫集 |
AnimatorInflater | 載入屬性動畫的XML檔案 |
一些額外的類
Java類 | 說明 |
---|---|
LayoutTransition | 佈局動畫,為佈局的容器設定動畫 |
ViewPropertyAnimator | 為View的動畫操作提供一種更加便捷的用法 |
PropertyValuesHolder | 儲存動畫過程中所需要操作的屬性和對應的值 |
Keyframe | 控制每個時間段執行的動畫距離 |
AnimationListener AnimationUpdateListener AnimatorListenerAdapter |
動畫事件的監聽 |
具體使用
ValueAnimator
- 屬性動畫的最核心的類
- 原理:控制 值 的變化,之後 手動 賦值給物件的屬性,從而實現動畫
對於控制的 值 的不同,Android 提供給我們三種構造方法來例項ValueAnimator物件
- ValueAnimator.ofInt(int... values) -- 整型數值
- ValueAnimator.ofFloat(float... values) -- 浮點型數值
- ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定義物件型別
下面我們一一介紹
ValueAnimator.ofInt()
- 作用:將初始值 以整型數值的形式 過渡到結束值
- 估值器:內建
IntEvaluator
估值器 - 具體使用:
操作 值 的方式分為 XML 方式/ Java 程式碼方式
方式1: Java 方式
推薦 Java 方式,因為某些時候我們需要動態獲取屬性的起始值,顯然XML方式是不支援動態獲取的。
//設定動畫 始 & 末值
//ofInt()兩個作用:
//1. 獲取例項
//2. 在傳入引數之間平滑過渡
//如下則0平滑過渡到3
ValueAnimator animator = ValueAnimator.ofInt(0,3);
//如下傳入多個引數,效果則為0->5,5->3,3->10
//ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);
//設定動畫的基礎屬性
animator.setDuration(5000);//播放時長
animator.setStartDelay(300);//延遲播放
animator.setRepeatCount(0);//重放次數
animator.setRepeatMode(ValueAnimator.RESTART);
//重放模式
//ValueAnimator.START:正序
//ValueAnimator.REVERSE:倒序
//設定更新監聽
//值 改變一次,該方法就執行一次
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//獲取改變後的值
int currentValue = (int) animation.getAnimatedValue();
//輸出改變後的值
Log.d("1111", "onAnimationUpdate: " + currentValue);
//改變後的值發賦值給物件的屬性值
view.setproperty(currentValue);
//重新整理檢視
view.requestLayout();
}
});
//啟動動畫
animator.start();
以上就是一個標準的Java方式的模板
方式2: XML 方式
- 在路徑
res/animator/
路徑下常見 XML 檔案,如set_animator.xml
- 在上述檔案中設定動畫引數
// ValueAnimator採用<animator> 標籤
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse"/>
/>
- Java程式碼啟動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
// 載入XML動畫
animator.setTarget(view);
// 設定動畫物件
animator.start();
// 啟動動畫
ValueAnimator.ofFloat()
- 作用:將初始值 以浮點型數值的形式 過渡到結束值
- 估值器:內建
FloatEvaluator
估值器 - 具體使用:
和ValueAnimator.ofInt()及其類似,以下只說明不同之處,省略部分參考ofInt()
方式1:Java方式
ValueAnimator anim = ValueAnimator.ofFloat(0, 3);
//只是改了例項方法,除此之外完全一樣
方式2:XML方式
只在設定動畫 XML 檔案中的屬性時略有不同
// ValueAnimator 採用 <animator> 標籤
// ObjectAnimator 採用 <objectAnimator> 標籤
<animatorxmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:valueTo="200"
android:valueType="floatType"
android:propertyName="y"
android:repeatCount="1"
android:repeatMode="reverse"/>
ValueAnimator.ofObject()
- 作用:將初始值 以物件的形式 過渡到結束值
- 估值器:Android 不提供,需要自定義估值器
- 具體使用:
ValueAnimator.ofObject() 屬於 ValueAnimator 的高階用法,我們前面提到的對任意物件進行動畫操作,就是通過此方法實現的。
以下先放上示例模板:
// 建立初始動畫的物件 & 結束動畫的物件
Point point1 = new Point ();
Point point2 = new Point ();
// 建立動畫物件 & 設定引數
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), point1 , point2 );
// 引數說明
// 1. 自定義的估值器物件(TypeEvaluator 型別引數) - 下面會詳細介紹
// 2. 初始動畫的物件
// 3. 結束動畫的物件
anim.setDuration(length);
anim.start();
上面示例中,我們看到有兩個Point物件point1 , point2 。假設我們有一個自定義View,這個View中有一個Point物件用於管理座標,然後我們在onDraw()方法中根據這個Point物件的座標值進行繪製,也就是說,我們可以對Point物件進行動畫操作,不停的根據Point的座標重新整理View重繪製,以此就可以實現 View 的動畫了。
到這裡都很好理解,不過我們還注意到傳入了一個 new myObjectEvaluator()
(TypeEvaluator
實現類) 引數,這是幹什麼的呢?不要急,下面我們就詳細解答。
TypeEvaluator 估值器
其實我們已經不止一次的提到估值器的概念,如 ValueAnimator.ofFloat()
方法中的 IntEvaluator
, ValueAnimator.ofFloat()
方法中的 FloatEvaluator
,這兩種都是Android預置供我們使用的,二者通過計算告知動畫系統如何從初始值過渡到結束值。
但是二者雖然好用,但也有其侷限性:只能針對 Int / Float 型別數值操作。因此某些我們需要對任意物件進行動畫操作的時候,二者顯然不能滿足我們的需求了,這時候我們需要自定義一個TypeEvaluator來告知系統如何進行過渡。
那麼如何自定義呢?別急,我們可以先看一看系統提供的 IntEvaluator
是如何實現的:
/**
* This evaluator can be used to perform type interpolation between int values.
*/
public class IntEvaluator implements TypeEvaluator<Integer> {
/**
* This function returns the result of linearly interpolating the start and end values, with
* fraction representing the proportion between the start and end values. The
* calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
* where x0 is startValue, x1 is endValue, and t is fraction.
*
* @param fraction The fraction from the starting to the ending values
* @param startValue The start value; should be of type int or Integer
* @param endValue The end value; should be of type int or Integer
* @return A linear interpolation between the start and end values, given the fraction parameter.
*/
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}
原始碼很簡單,註釋也相當詳細,方便觀看,我再次解釋一下:
IntEvaluator
實現了 TypeEvaluator
介面,然後重寫了 evaluate()
方法。該方法的三個引數意義如下:
- fraction:表示動畫完成度,據此計算當前動畫的值
- startValue:動畫初始值
- endValue:動畫結束值
那麼 evalute()
方法的返回值就不難理解了,簡單的數學公式,返回當前動畫的值。
是不是很簡單?因此我們自定義 TypeEvaluator
時只需要實現 TypeEvaluator
介面,然後重寫 evaluate()
方法,在此方法中處理好邏輯即可。
下面我們就動手寫一個自定義 TypeEvaluator
自定義TypeEvaluator
我們還是以上面提過的Point物件管理View座標的為例:
- 定義
Point
類
public class Point {
//記錄座標位置
private float x;
private float y;
//通過構造方法設定座標,因此不需要額外的set方法
public Point(float x, float y) {
this.x = x;
this.y = y;
}
//get方法,獲取當前座標值
public float getX() {
return x;
}
public float getY() {
return y;
}
}
- 定義
PointEvaluator
//實現TypeEvaluator介面
public class PointEvaluator implements TypeEvaluator {
//重寫evaluate()方法
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
//始末值強轉為Point物件
Point startPoint = (Point) startValue;
Point endPoint = (Point) endValue;
//通過fraction計算當前動畫的座標值x,y
float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
//返回以上述x,y組裝的新的Point物件
Point point = new Point(x,y);
return point;
}
}
Point
物件間的平滑過渡
// 建立初始動畫的物件 & 結束動畫的物件
Point point1 = new Point(0, 0);
Point point2 = new Point(500, 500);
// 建立動畫物件 & 設定引數
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1 , point2 );
anim.setDuration(3000);
anim.start();
以上就是自定義TypeEvaluator的全部用法。
下面我們就可以嘗試用上述知識練習如何對物件進行動畫操作,從而實現自定義View的動畫效果
自定義View的動畫效果
- 新建MyAnimView繼承View
public class MyAnimView extends View {
//常量
public static final float RADIUS = 50f;
//當前Point,記錄當前動畫的值(x,y座標)
private Point curPoint;
//畫筆
private Paint mPaint;
//Java程式碼例項化View時呼叫
public MyAnimView(Context context) {
super(context);
}
//XML檔案例項時呼叫
public MyAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
//初始化畫筆
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
//第一次繪製時
if (curPoint == null){
//初始化座標為(50f, 50f)
curPoint = new Point(RADIUS,RADIUS);
//畫圓
drawCircle(canvas);
//開始動畫
startAnimation();
}else {//非第一次繪製
drawCircle(canvas);
}
}
//在當前座標處繪製一個半徑為50f的圓
private void drawCircle(Canvas canvas) {
float x = curPoint.getX();
float y = curPoint.getY();
canvas.drawCircle(x,y,RADIUS,mPaint);
}
//開始動畫
private void startAnimation() {
//設定 起始值&結束值
Point startPoint = new Point(RADIUS,RADIUS);//起始為左上角(50f,50f)
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//終點為右下角(螢幕寬度-50f,螢幕高度-50f)
final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
//設定插值器
anim.setInterpolator(new BounceInterpolator());
//設定監聽
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//每當Point的值有改變的時候,都會呼叫onAnimationUpdate()方法
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//更新curPoint,即更新當前座標
curPoint = (Point) animation.getAnimatedValue();
// 重新整理,重現呼叫onDraw()方法
// 由於curPoint的值改變,那麼繪製的位置也會改變,也就實現了一個從左上到右下的平移動畫
invalidate();
}
});
anim.setDuration(5000);
anim