| 關(guān)鍵時(shí)刻,第一時(shí)間送達(dá)! 
 
 前言動(dòng)畫(huà)的使用 是 Android 開(kāi)發(fā)中常用的知識(shí)可是動(dòng)畫(huà)的種類(lèi)繁多、使用復(fù)雜,每當(dāng)需要 采用自定義動(dòng)畫(huà)實(shí)現(xiàn)復(fù)雜的動(dòng)畫(huà)效果時(shí),很多開(kāi)發(fā)者就顯得束手無(wú)策Android中 補(bǔ)間動(dòng)畫(huà) & 屬性動(dòng)畫(huà)實(shí)現(xiàn)動(dòng)畫(huà)的原理是:
 
 目錄
 1. 插值器(Interpolator)1.1 簡(jiǎn)介如勻速、加速 & 減速 等等即確定了 動(dòng)畫(huà)效果變化的模式,如勻速變化、加速變化 等等
 1.2 應(yīng)用場(chǎng)景實(shí)現(xiàn)非線性運(yùn)動(dòng)的動(dòng)畫(huà)效果 非線性運(yùn)動(dòng):動(dòng)畫(huà)改變的速率不是一成不變的,如加速 & 減速運(yùn)動(dòng)都屬于非線性運(yùn)動(dòng)
 1.3 具體使用a. 設(shè)置方式 插值器在動(dòng)畫(huà)的使用有兩種方式:在XML / Java代碼中設(shè)置: 設(shè)置方法1:在 動(dòng)畫(huà)效果的XML代碼中設(shè)置插值器屬性android:interpolator '1.0' encoding='utf-8'?>scale xmlns:android='http://schemas./apk/res/android'
 
 android:interpolator='@android:anim/overshoot_interpolator'
 // 通過(guò)資源ID設(shè)置插值器
 android:duration='3000'
 android:fromXScale='0.0'
 android:fromYScale='0.0'
 android:pivotX='50%'
 android:pivotY='50%'
 android:toXScale='2'
 android:toYScale='2' />
 設(shè)置方法2:在 Java 代碼中設(shè)置 Button mButton = (Button) findViewById(R.id.Button);// 步驟1:創(chuàng)建 需要設(shè)置動(dòng)畫(huà)的 視圖View
 
 Animation alphaAnimation = new AlphaAnimation(1,0);
 // 步驟2:創(chuàng)建透明度動(dòng)畫(huà)的對(duì)象 & 設(shè)置動(dòng)畫(huà)效果
 
 alphaAnimation.setDuration(3000);
 Interpolator overshootInterpolator = new OvershootInterpolator();
 // 步驟3:創(chuàng)建對(duì)應(yīng)的插值器類(lèi)對(duì)象
 
 alphaAnimation.setInterpolator(overshootInterpolator);
 // 步驟4:給動(dòng)畫(huà)設(shè)置插值器
 
 mButton.startAnimation(alphaAnimation);
 // 步驟5:播放動(dòng)畫(huà)
 b. 系統(tǒng)內(nèi)置插值器類(lèi)型 
 使用時(shí): 系統(tǒng)默認(rèn)的插值器是AccelerateDecelerateInterpolator,即先加速后減速
 
 使用Android內(nèi)置的插值器能滿足大多數(shù)的動(dòng)畫(huà)需求如果上述9個(gè)插值器無(wú)法滿足需求,還可以自定義插值器下面將介紹如何自定義插值器(Interpolator)
 c. 自定義插值器 補(bǔ)間動(dòng)畫(huà) 實(shí)現(xiàn) Interpolator接口;屬性動(dòng)畫(huà)實(shí)現(xiàn)TimeInterpolator接口TimeInterpolator接口是屬性動(dòng)畫(huà)中新增的,用于兼容Interpolator接口,這使得所有過(guò)去的Interpolator實(shí)現(xiàn)類(lèi)都可以直接在屬性動(dòng)畫(huà)使用
 // Interpolator接口public interface Interpolator {
 
 // 內(nèi)部只有一個(gè)方法
 float getInterpolation(float input) {
 // 參數(shù)說(shuō)明
 // input值值變化范圍是0-1,且隨著動(dòng)畫(huà)進(jìn)度(0% - 100% )均勻變化
 // 即動(dòng)畫(huà)開(kāi)始時(shí),input值 = 0;動(dòng)畫(huà)結(jié)束時(shí)input = 1
 // 而中間的值則是隨著動(dòng)畫(huà)的進(jìn)度(0% - 100%)在0到1之間均勻增加
 
 ...// 插值器的計(jì)算邏輯
 
 return xxx;
 // 返回的值就是用于估值器繼續(xù)計(jì)算的fraction值,下面會(huì)詳細(xì)說(shuō)明    }
 
 // TimeInterpolator接口
 // 同上
 public interface TimeInterpolator {
 float getInterpolation(float input);
 }
 在學(xué)習(xí)自定義插值器前,我們先來(lái)看兩個(gè)已經(jīng)實(shí)現(xiàn)好的系統(tǒng)內(nèi)置差值器: // 勻速差值器:LinearInterpolator@HasNativeInterpolator
 public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
 
 // 僅貼出關(guān)鍵代碼
 ...
 public float getInterpolation(float input) {
 
 return input;
 // 沒(méi)有對(duì)input值進(jìn)行任何邏輯處理,直接返回
 // 即input值 = fraction值
 // 因?yàn)閕nput值是勻速增加的,因此fraction值也是勻速增加的,所以動(dòng)畫(huà)的運(yùn)動(dòng)情況也是勻速的,所以是勻速插值器
 }
 
 // 先加速再減速 差值器:AccelerateDecelerateInterpolator
 @HasNativeInterpolator
 public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
 // 僅貼出關(guān)鍵代碼
 ...
 public float getInterpolation(float input) {
 return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
 // input的運(yùn)算邏輯如下:
 // 使用了余弦函數(shù),因input的取值范圍是0到1,那么cos函數(shù)中的取值范圍就是π到2π。
 // 而cos(π)的結(jié)果是-1,cos(2π)的結(jié)果是1
 // 所以該值除以2加上0.5后,getInterpolation()方法最終返回的結(jié)果值還是在0到1之間。只不過(guò)經(jīng)過(guò)了余弦運(yùn)算之后,最終的結(jié)果不再是勻速增加的了,而是經(jīng)歷了一個(gè)先加速后減速的過(guò)程
 // 所以最終,fraction值 = 運(yùn)算后的值 = 先加速后減速
 // 所以該差值器是先加速再減速的
 }
 
 }
 實(shí)例步驟1:根據(jù)需求實(shí)現(xiàn)Interpolator接口DecelerateAccelerateInterpolator.java
 /*** Created by Carson_Ho on 17/4/19.
 */
 
 public class DecelerateAccelerateInterpolator implements TimeInterpolator {
 
 @Override
 public float getInterpolation(float input) {
 float result;
 if (input <=>=>0.5) {
 result = (float) (Math.sin(Math.PI * input)) / 2;
 // 使用正弦函數(shù)來(lái)實(shí)現(xiàn)先減速后加速的功能,邏輯如下:
 // 因?yàn)檎液瘮?shù)初始弧度變化值非常大,剛好和余弦函數(shù)是相反的
 // 隨著弧度的增加,正弦函數(shù)的變化值也會(huì)逐漸變小,這樣也就實(shí)現(xiàn)了減速的效果。
 // 當(dāng)弧度大于π/2之后,整個(gè)過(guò)程相反了過(guò)來(lái),現(xiàn)在正弦函數(shù)的弧度變化值非常小,漸漸隨著弧度繼續(xù)增加,變化值越來(lái)越大,弧度到π時(shí)結(jié)束,這樣從0過(guò)度到π,也就實(shí)現(xiàn)了先減速后加速的效果
 } else {
 result = (float) (2 - Math.sin(Math.PI * input)) / 2;
 }
 return result;
 // 返回的result值 = 隨著動(dòng)畫(huà)進(jìn)度呈先減速后加速的變化趨勢(shì)
 }
 }
 MainActivity.java  mButton = (Button) findViewById(R.id.Button);// 創(chuàng)建動(dòng)畫(huà)作用對(duì)象:此處以Button為例
 
 float curTranslationX = mButton.getTranslationX();
 // 獲得當(dāng)前按鈕的位置
 
 ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, 'translationX', curTranslationX, 300,curTranslationX);
 // 創(chuàng)建動(dòng)畫(huà)對(duì)象 & 設(shè)置動(dòng)畫(huà)
 // 表示的是:
 // 動(dòng)畫(huà)作用對(duì)象是mButton
 // 動(dòng)畫(huà)作用的對(duì)象的屬性是X軸平移
 // 動(dòng)畫(huà)效果是:從當(dāng)前位置平移到 x=1500 再平移到初始位置
 animator.setDuration(5000);
 animator.setInterpolator(new DecelerateAccelerateInterpolator());
 // 設(shè)置插值器
 animator.start();
 // 啟動(dòng)動(dòng)畫(huà)
 效果圖  
 2. 估值器(TypeEvaluator)2.1 簡(jiǎn)介插值器(Interpolator)決定 值 的變化規(guī)律(勻速、加速blabla),即決定的是變化趨勢(shì);而接下來(lái)的具體變化數(shù)值則交給而估值器
屬性動(dòng)畫(huà)特有的屬性
 2.2 應(yīng)用場(chǎng)景協(xié)助插值器 實(shí)現(xiàn)非線性運(yùn)動(dòng)的動(dòng)畫(huà)效果 非線性運(yùn)動(dòng):動(dòng)畫(huà)改變的速率不是一成不變的,如加速 & 減速運(yùn)動(dòng)都屬于非線性運(yùn)動(dòng)
 2.3 具體使用a. 設(shè)置方式 ObjectAnimator anim = ObjectAnimator.ofObject(myView2, 'height', new Evaluator(),1,3);// 在第4個(gè)參數(shù)中傳入對(duì)應(yīng)估值器類(lèi)的對(duì)象// 系統(tǒng)內(nèi)置的估值器有3個(gè):
 // IntEvaluator:以整型的形式從初始值 - 結(jié)束值 進(jìn)行過(guò)渡
 // FloatEvaluator:以浮點(diǎn)型的形式從初始值 - 結(jié)束值 進(jìn)行過(guò)渡
 // ArgbEvaluator:以Argb類(lèi)型的形式從初始值 - 結(jié)束值 進(jìn)行過(guò)渡
  
 
 b. 自定義估值器 如:動(dòng)畫(huà)進(jìn)行了50%(初始值=100,結(jié)束值=200 ),那么勻速插值器計(jì)算出了當(dāng)前屬性值改變的百分比是50%,那么估值器則負(fù)責(zé)計(jì)算當(dāng)前屬性值 = 100 + (200-100)x50% = 150.
 public interface TypeEvaluator {
 public Object evaluate(float fraction, Object startValue, Object endValue) {
 // 參數(shù)說(shuō)明// fraction:插值器getInterpolation()的返回值
 // startValue:動(dòng)畫(huà)的初始值
 // endValue:動(dòng)畫(huà)的結(jié)束值
 
 ....// 估值器的計(jì)算邏輯
 
 return xxx;
 // 賦給動(dòng)畫(huà)屬性的具體數(shù)值
 // 使用反射機(jī)制改變屬性變化
 
 // 特別注意
 // 那么插值器的input值 和 估值器fraction有什么關(guān)系呢?
 // 答:input的值決定了fraction的值:input值經(jīng)過(guò)計(jì)算后傳入到插值器的getInterpolation(),然后通過(guò)實(shí)現(xiàn)getInterpolation()中的邏輯算法,根據(jù)input值來(lái)計(jì)算出一個(gè)返回值,而這個(gè)返回值就是fraction了
 }
 }
 在學(xué)習(xí)自定義插值器前,我們先來(lái)看一個(gè)已經(jīng)實(shí)現(xiàn)好的系統(tǒng)內(nèi)置差值器:浮點(diǎn)型插值器:FloatEvaluator public class FloatEvaluator implements TypeEvaluator {// FloatEvaluator實(shí)現(xiàn)了TypeEvaluator接口
 
 // 重寫(xiě)evaluate()
 
 public Object evaluate(float fraction, Object startValue, Object endValue) {
 // 參數(shù)說(shuō)明
 // fraction:表示動(dòng)畫(huà)完成度(根據(jù)它來(lái)計(jì)算當(dāng)前動(dòng)畫(huà)的值)
 // startValue、endValue:動(dòng)畫(huà)的初始值和結(jié)束值
 float startFloat = ((Number) startValue).floatValue();
 
 return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);
 // 初始值 過(guò)渡 到結(jié)束值 的算法是:
 // 1. 用結(jié)束值減去初始值,算出它們之間的差值
 // 2. 用上述差值乘以fraction系數(shù)
 // 3. 再加上初始值,就得到當(dāng)前動(dòng)畫(huà)的值
 }
 }
 屬性動(dòng)畫(huà)中的ValueAnimator.ofInt() & ValueAnimator.ofFloat()都具備系統(tǒng)內(nèi)置的估值器,即FloatEvaluator & IntEvaluator 即系統(tǒng)已經(jīng)默認(rèn)實(shí)現(xiàn)了 如何從初始值 過(guò)渡到 結(jié)束值 的邏輯
但對(duì)于ValueAnimator.ofObject(),從上面的工作原理可以看出并沒(méi)有系統(tǒng)默認(rèn)實(shí)現(xiàn),因?yàn)閷?duì)對(duì)象的動(dòng)畫(huà)操作復(fù)雜 & 多樣,系統(tǒng)無(wú)法知道如何從初始對(duì)象過(guò)度到結(jié)束對(duì)象因此,對(duì)于ValueAnimator.ofObject(),我們需自定義估值器(TypeEvaluator)來(lái)告知系統(tǒng)如何進(jìn)行從 初始對(duì)象 過(guò)渡到 結(jié)束對(duì)象的邏輯自定義實(shí)現(xiàn)的邏輯如下
 // 實(shí)現(xiàn)TypeEvaluator接口public class ObjectEvaluator implements TypeEvaluator{
 
 // 復(fù)寫(xiě)evaluate()
 // 在evaluate()里寫(xiě)入對(duì)象動(dòng)畫(huà)過(guò)渡的邏輯
 @Override
 public Object evaluate(float fraction, Object startValue, Object endValue) {
 // 參數(shù)說(shuō)明
 // fraction:表示動(dòng)畫(huà)完成度(根據(jù)它來(lái)計(jì)算當(dāng)前動(dòng)畫(huà)的值)
 // startValue、endValue:動(dòng)畫(huà)的初始值和結(jié)束值
 
 ... // 寫(xiě)入對(duì)象動(dòng)畫(huà)過(guò)渡的邏輯
 
 return value;
 // 返回對(duì)象動(dòng)畫(huà)過(guò)渡的邏輯計(jì)算后的值
 
 }
 實(shí)例說(shuō)明
 
 步驟1:定義對(duì)象類(lèi) public class Point {
 // 設(shè)置兩個(gè)變量用于記錄坐標(biāo)的位置
 private float x;
 private float y;
 
 // 構(gòu)造方法用于設(shè)置坐標(biāo)
 public Point(float x, float y) {
 this.x = x;
 this.y = y;
 }
 
 // get方法用于獲取坐標(biāo)
 public float getX() {
 return x;
 }
 
 public float getY() {
 return y;
 }
 }
 步驟2:根據(jù)需求實(shí)現(xiàn)TypeEvaluator接口 
 PointEvaluator.java // 實(shí)現(xiàn)TypeEvaluator接口public class PointEvaluator implements TypeEvaluator {
 // 復(fù)寫(xiě)evaluate()
 // 在evaluate()里寫(xiě)入對(duì)象動(dòng)畫(huà)過(guò)渡的邏輯
 @Override
 public Object evaluate(float fraction, Object startValue, Object endValue) {
 // 將動(dòng)畫(huà)初始值startValue 和 動(dòng)畫(huà)結(jié)束值endValue 強(qiáng)制類(lèi)型轉(zhuǎn)換成Point對(duì)象
 Point startPoint = (Point) startValue;
 Point endPoint = (Point) endValue;
 
 // 根據(jù)fraction來(lái)計(jì)算當(dāng)前動(dòng)畫(huà)的x和y的值
 float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
 float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());
 
 // 將計(jì)算后的坐標(biāo)封裝到一個(gè)新的Point對(duì)象中并返回
 Point point = new Point(x, y);
 return point;
 }
 
 }
 步驟3:將屬性動(dòng)畫(huà)作用到自定義View當(dāng)中 MyView.java /*** Created by Carson_Ho on 17/4/18.
 */
 public class MyView extends View {
 // 設(shè)置需要用到的變量
 public static final float RADIUS = 70f;// 圓的半徑 = 70
 private Point currentPoint;// 當(dāng)前點(diǎn)坐標(biāo)
 private Paint mPaint;// 繪圖畫(huà)筆
 
 // 構(gòu)造方法(初始化畫(huà)筆)
 public MyView(Context context, AttributeSet attrs) {
 super(context, attrs);
 // 初始化畫(huà)筆
 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 mPaint.setColor(Color.BLUE);
 }
 
 // 復(fù)寫(xiě)onDraw()從而實(shí)現(xiàn)繪制邏輯
 // 繪制邏輯:先在初始點(diǎn)畫(huà)圓,通過(guò)監(jiān)聽(tīng)當(dāng)前坐標(biāo)值(currentPoint)的變化,每次變化都調(diào)用onDraw()重新繪制圓,從而實(shí)現(xiàn)圓的平移動(dòng)畫(huà)效果
 @Override
 protected void onDraw(Canvas canvas) {
 // 如果當(dāng)前點(diǎn)坐標(biāo)為空(即第一次)
 if (currentPoint == null) {
 currentPoint = new Point(RADIUS, RADIUS);
 // 創(chuàng)建一個(gè)點(diǎn)對(duì)象(坐標(biāo)是(70,70))
 
 // 在該點(diǎn)畫(huà)一個(gè)圓:圓心 = (70,70),半徑 = 70
 float x = currentPoint.getX();
 float y = currentPoint.getY();
 canvas.drawCircle(x, y, RADIUS, mPaint);
 
 // (重點(diǎn)關(guān)注)將屬性動(dòng)畫(huà)作用到View中
 // 步驟1:創(chuàng)建初始動(dòng)畫(huà)時(shí)的對(duì)象點(diǎn)  & 結(jié)束動(dòng)畫(huà)時(shí)的對(duì)象點(diǎn)
 Point startPoint = new Point(RADIUS, RADIUS);// 初始點(diǎn)為圓心(70,70)
 Point endPoint = new Point(700, 1000);// 結(jié)束點(diǎn)為(700,1000)
 
 // 步驟2:創(chuàng)建動(dòng)畫(huà)對(duì)象 & 設(shè)置初始值 和 結(jié)束值
 ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
 // 參數(shù)說(shuō)明
 // 參數(shù)1:TypeEvaluator 類(lèi)型參數(shù) - 使用自定義的PointEvaluator(實(shí)現(xiàn)了TypeEvaluator接口)
 // 參數(shù)2:初始動(dòng)畫(huà)的對(duì)象點(diǎn)
 // 參數(shù)3:結(jié)束動(dòng)畫(huà)的對(duì)象點(diǎn)
 // 步驟3:設(shè)置動(dòng)畫(huà)參數(shù)
 anim.setDuration(5000);
 // 設(shè)置動(dòng)畫(huà)時(shí)長(zhǎng)
 
 // 步驟3:通過(guò) 值 的更新監(jiān)聽(tīng)器,將改變的對(duì)象手動(dòng)賦值給當(dāng)前對(duì)象
 // 此處是將 改變后的坐標(biāo)值對(duì)象 賦給 當(dāng)前的坐標(biāo)值對(duì)象
 // 設(shè)置 值的更新監(jiān)聽(tīng)器
 // 即每當(dāng)坐標(biāo)值(Point對(duì)象)更新一次,該方法就會(huì)被調(diào)用一次
 anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
 
 @Override
 public void onAnimationUpdate(ValueAnimator animation) {
 currentPoint = (Point) animation.getAnimatedValue();
 // 將每次變化后的坐標(biāo)值(估值器PointEvaluator中evaluate()返回的Piont對(duì)象值)到當(dāng)前坐標(biāo)值對(duì)象(currentPoint)
 // 從而更新當(dāng)前坐標(biāo)值(currentPoint)
 
 // 步驟4:每次賦值后就重新繪制,從而實(shí)現(xiàn)動(dòng)畫(huà)效果
 invalidate();
 // 調(diào)用invalidate()后,就會(huì)刷新View,即才能看到重新繪制的界面,即onDraw()會(huì)被重新調(diào)用一次
 // 所以坐標(biāo)值每改變一次,就會(huì)調(diào)用onDraw()一次
 }
 });
 
 anim.start();
 // 啟動(dòng)動(dòng)畫(huà)
 
 } else {
 // 如果坐標(biāo)值不為0,則畫(huà)圓
 // 所以坐標(biāo)值每改變一次,就會(huì)調(diào)用onDraw()一次,就會(huì)畫(huà)一次圓,從而實(shí)現(xiàn)動(dòng)畫(huà)效果
 
 // 在該點(diǎn)畫(huà)一個(gè)圓:圓心 = (30,30),半徑 = 30
 float x = currentPoint.getX();
 float y = currentPoint.getY();
 canvas.drawCircle(x, y, RADIUS, mPaint);
 }
 }
 }
 步驟4:在布局文件加入自定義View空間 activity_main.xml '1.0' encoding='utf-8'?>RelativeLayout xmlns:android='http://schemas./apk/res/android'
 
 xmlns:tools='http://schemas./tools'
 android:layout_width='match_parent'
 android:layout_height='match_parent'
 android:paddingBottom='@dimen/activity_vertical_margin'
 android:paddingLeft='@dimen/activity_horizontal_margin'
 android:paddingRight='@dimen/activity_horizontal_margin'
 android:paddingTop='@dimen/activity_vertical_margin'
 tools:context='scut.carson_ho.valueanimator_ofobject.MainActivity'>
 
 scut.carson_ho.valueanimator_ofobject.MyView
 android:layout_width='match_parent'
 android:layout_height='match_parent'
 />
 RelativeLayout>
 步驟5:在主代碼文件設(shè)置顯示視圖 MainActivity.java public class MainActivity extends AppCompatActivity {
 @Override
 protected void onCreate(Bundle savedInstanceState) {
 super.onCreate(savedInstanceState);
 setContentView(R.layout.activity_main);
 }
 }
 
 源碼地址:Carson_Ho的Github地址 
 
 
 
  【點(diǎn)擊成為Python大神】
 |