帶有活力的屬性動畫源碼分析與實戰——Android高級UI

終於在新的一年的第一天完成了本篇文章,小盆友在此祝賀您,萬事如意,闔家幸福。😄java

目錄android

1、前言git

2、插值器與估值器程序員

3、源碼解析github

4、實戰canvas

5、寫在最後設計模式

1、前言

對於愈來愈追求豐富的交互體驗的客戶端,一個帶有動態效果的界面已是不可避免。屬性動畫就是這其中必不可少的一把利器,因此今天便來分享下 屬性動畫融合在實際場景中 的使用,以及進行 源碼分析。話很少說,先看看今天的實戰效果圖,而後開始進行分享之旅。數組

一、多維雷達圖 瀏覽器

在這裏插入圖片描述

二、錶盤指示器 微信

三、活力四射的購物車

老規矩,代碼在實戰篇會給出,透過 API 看清源碼,各類效果即是順手拈來😄

2、插值器與估值器

對於屬性動畫來講,必定是繞不開 插值器估值器。接下來便一一道來

文章更多的使用 我在開發過程當中 自我理解 的詞語,而儘可能不使用 教科書式直接翻譯註釋 語句。若是有 晦澀難懂 或是 理解錯誤之處,歡迎 評論區 留言,咱們進行探討。

一、插值器(TimeInterpolator)

(1)定義

是一個控制動畫 進度 的工具。

爲什麼如此說?我們能夠這麼理解,咱們藉助 SpringInterpolator 插值器的函數圖來說解。

  • x軸爲 時間 。0表示還未開始,1表示時間結束。時間是 不可逆的 ,因此會一直往前推動,即 只會增長
  • y軸爲 當前時間點,物體(咱們的控制的動畫對象,例如View)距離目的地的進度。0表示進度還未走動,1表示進度已經走至目的地。但值得注意的是,到達目的地(A點)不表明動畫已經結束,能夠 「衝破」(B點) 目的地,也能夠 往回走(C點)結束與否是由時間軸決定。

SpringInterpolator 的動態圖以下

下面動態圖是小盆友專門爲插值器更爲直觀的展現而開發的小工具,若是你感興趣,能夠把本身的插值器也放入這其中進行展現,方便之後開發時須要,代碼傳送門

這個插值器,在小盆友的另外一篇博文中有使用到,效果以下圖,有興趣的童鞋能夠進入 傳送門瞭解。

(2)如何使用

Android 系統中已經幫我實現了一些比較經常使用插值器,這裏就不一一貼圖介紹器函數圖,須要的童鞋能夠進入小盆友下面所提到的 插值器小工具 進行玩耍,這裏給一張工具的效果圖。

加入到屬性動畫中也很簡單,只需如下一行代碼,即可輕鬆搞定

// 將 插值器 設置進 Animator 
mAnimator.setInterpolator(new AccelerateInterpolator());
複製代碼

在源碼中插值器是如何做用的呢?先賣個關子,在源碼解析小節給出答案。

可是在某些狀況下,爲了知足動畫效果的須要,Android提供的插值器就知足不了咱們(😭其實就是設計師搞事情)了,因此須要找到對應的公式進行自定義插值器。聰明的你,確定已經發現,咱們前面提到的 SpringInterpolator 並非Android提供的插值器。定義 SpringInterpolator 時,只須要實現 TimeInterpolator 接口,並在 getInterpolation 方法中實現本身的邏輯便可,代碼以下

/** * @author Jiang zinc * @date 建立時間:2019/1/27 * @description 震旦效果 */
public class SpringInterpolator implements TimeInterpolator {

    /** * 參數 x,即爲 x軸的值 * 返回值 即是 y 軸的值 */
    @Override
    public float getInterpolation(float x) {
        float factor = 0.4f;
        return (float) (Math.pow(2, -10 * x) * Math.sin((x - factor / 4) * (2 * Math.PI) / factor) + 1);
    }
}
複製代碼

使用時,和 Android提供的插值器 是一摸同樣的,以下所示

mAnimator.setInterpolator(new SpringInterpolator());
複製代碼

二、估值器(TypeEvaluator)

(1)定義

插值器 中的 y軸數值 轉換爲咱們 須要的值類型 的工具。

emmmm....稍微有點抽象。咱們來具體分析下,這句話的意思。

咱們以一個 具體的場景 來分析這個定義,方便講解也更容易理解。咱們進行旋轉一個View,在1秒內,從 0度 轉到 360度。具體代碼以下:

// 實例化一個view
View view = new View(this);
// 設置 屬性動畫 的目標對象、做用的屬性、關鍵幀(即0,360)
// 做用的屬性值 rotation 會轉爲對應的方法名 setRotation,這個是默認的規則。
ObjectAnimator rotationAnimator = ObjectAnimator.ofFloat(view, "rotation", 0, 360);
// 設置 插值器
rotationAnimator.setInterpolator(new SpringInterpolator());
// 設置 動畫時長
rotationAnimator.setDuration(1_000);
// 開啓 動畫
rotationAnimator.start();
複製代碼

這裏其實有個問題,童鞋們應該也注意到了,插值器 的返回值(即函數圖中的 y軸數值)是一個 到 「目的地」 的距離百分比(這裏的百分比也就是咱們前面所說的進度) ,而非咱們例子中須要度數,因此須要進行 轉換,而起到轉換做用的就是咱們的 估值器

怎麼轉換呢?這裏要 分兩種狀況說明

第一種狀況,咱們經過如下代碼,設置一個估值器,則計算規則由設置的估值器肯定

ObjectAnimator mTranslateAnimator = ObjectAnimator.ofObject(view,
													        "position",
													        new BezierEvaluator(),
													        startPoint,
													        endPoint);
複製代碼

Android 系統中提供了一些 經常使用的估值器

  • ArgbEvaluator:顏色估值器,能夠用於從 開始顏色 漸變爲 終止顏色;
  • FloatArrayEvaluator:浮點數組估值器,將開始浮點數組 逐漸變爲 終止浮點數組;
  • FloatEvaluator:浮點數值估值器,將開始浮點數 逐漸變爲 終止浮點數;
  • IntArrayEvaluator:整型數組估值器,將開始整型數組 逐漸變爲 終止整型數組;
  • IntEvaluator:整型數值估值器,將開始整型數 逐漸變爲 終止整型數;
  • PointFEvaluator:座標點估值器,將開始座標點 逐漸變爲 終止座標點;
  • RectEvaluator:範圍估值器,將開始範圍 逐漸變爲 終止範圍;

然鵝,在某些狀況下(產品大大搞事),咱們須要實現一個相似以下的添加到購物車的效果,商品是以 貝塞爾曲線 的路徑 「投到」 購物車中的,這時咱們就須要 自定義估值器,由於 PointFEvaluator只是線性的將商品從起始點移到終止點,知足不了產品大大的需求,如何自定義呢?請往下走

對貝塞爾曲線感興趣的童鞋,能夠查看小盆友的另外一片博文 自帶美感的貝塞爾曲線原理與實戰

相信你也猜到了,估值器 也是經過實現一個接口,如下即是接口和參數描述

public interface TypeEvaluator<T> {
	/** * @param fraction 插值器返回的值(即函數圖中的 y軸數值) * @param startValue 動畫屬性起始值,例子中的 0度 * @param endValue 動畫屬性終止值,例子中的 360度 */
    public T evaluate(float fraction, T startValue, T endValue);
}
複製代碼

咱們只須要實現他,填充本身的邏輯,以這個購物車的路徑爲例,即是如下代碼,這樣一個走 貝塞爾曲線 路徑的商品就出現了。

private static class BezierEvaluator implements TypeEvaluator<PointF> {

    private final List<PointF> pointList;

    public BezierEvaluator(PointF startPoint, PointF endPoint) {
        this.pointList = new ArrayList<>();

        PointF controlPointF = new PointF(endPoint.x, startPoint.y);

        pointList.add(startPoint);
        pointList.add(controlPointF);
        pointList.add(endPoint);

    }

    @Override
    public PointF evaluate(float fraction, PointF startPoint, PointF endPoint) {
        return new PointF(BezierUtils.calculatePointCoordinate(BezierUtils.X_TYPE, fraction, 2, 0, pointList),
                BezierUtils.calculatePointCoordinate(BezierUtils.Y_TYPE, fraction, 2, 0, pointList));
    }
}
複製代碼

購物車的動畫思路以下:

  1. 點擊添加商品後,初始化一個 ShoppingView(繼承至ImageView),設置其大小和商品的大小一致,並添加至 decorView 中,這樣才能讓 ShoppingView 在整個視圖中移動。
  2. 經過 getLocationOnScreen 方法,獲取商品圖片在屏幕中的座標A 和 購物車在屏幕中的座標B(值得注意的是,這個座標是 包含狀態欄的高度 ),將 ShoppingView 移至座標A,而後啓動動畫。
  3. 進行以 ShoppingView 正中心爲原點進行 從0到0.8縮小,接着順時針傾斜35度,緊接着以 貝塞爾曲線 路徑從座標A移至座標B,控制點C的x軸爲B點的x軸座標,y軸爲A點的y軸座標;
  4. 動畫完成後,將 ShoppingView 從 其父視圖中移除,同時回調偵聽接口,以便購物車能夠進行動畫。
  5. 接到回調後,購物車數量加一,同時更換購物車圖標,已經顯示小紅點而且播放其動畫,以小紅點正中心爲原點進行縮放,縮放規則爲從 1 到 1.2 到 1 到 1.1 到 1, 產生一種彈動的效果。

若是對這一動畫感興趣,能夠查看具體代碼,請進入傳送門

第二種狀況,大多數狀況下,咱們不會進行設置估值器,由於源碼中已經幫咱們作了這一步的轉換。因此當咱們沒有設置時,系統會以如下的公式進行轉換(咱們這裏以 浮點數 爲具體場景)

// 這段代碼在 FloatKeyframeSet 的 getFloatValue 方法中
prevValue + intervalFraction * (nextValue - prevValue);
複製代碼

這裏再賣個關子,公式的各個參數的意義先不給出,在源碼解析一節中一塊兒講解。

值得注意的是,若是屬性動畫中須要使用的是本身定義的類型,則必需要使用第一種狀況自行定義估值器,不然會crash。

(2)如何使用

如何使用,在上一小節其實已經給出,這裏給一個完整的代碼

ObjectAnimator mTranslateAnimator = ObjectAnimator.ofObject(this,
                "position",
                new BezierEvaluator(startPoint, endPoint),
                startPoint,
                endPoint);
mTranslateAnimator.setDuration(450);
mTranslateAnimator.setInterpolator(new AccelerateInterpolator());
mTranslateAnimator.start();

private static class BezierEvaluator implements TypeEvaluator<PointF> {

    private final List<PointF> pointList;

    public BezierEvaluator(PointF startPoint, PointF endPoint) {
        this.pointList = new ArrayList<>();

        PointF controlPointF = new PointF(endPoint.x, startPoint.y);

        pointList.add(startPoint);
        pointList.add(controlPointF);
        pointList.add(endPoint);

    }

    @Override
    public PointF evaluate(float fraction, PointF startPoint, PointF endPoint) {
        return new PointF(BezierUtils.calculatePointCoordinate(BezierUtils.X_TYPE, fraction, 2, 0, pointList),
                BezierUtils.calculatePointCoordinate(BezierUtils.Y_TYPE, fraction, 2, 0, pointList));
    }
}
複製代碼

3、源碼解析

一、約法三章

進入源碼解析前,有必要先跟各位同窗 確認一件事創建一個場景,不然源碼解析過程會 沒有目標 ,而迷路。

(1)確認一件事

你已經會使用屬性動畫,會的意思是你已經能在 不借助文檔 或是 「借鑑」別人的代碼 的狀況下,寫出一個屬性動畫,並讓他按照你所須要的效果 正常的運行 起來,效果難易程度不限定。

(2)創建一個場景

源碼的閱讀在一個具體的場景中更容易理解,雖然會稍微片面些,可是漏掉的情景在懂得了一個場景後,後續使用過程當中便會慢慢的補充,並且主線已懂,支線也就不難了。話很少說,咱們來看看這個使用場景。

咱們針對 ZincView 的 setZinc 方法進行值變更:

  • 值變更範圍:從 0 到 2,從 2 到 5(浮點數)
  • 動畫時長 2000 毫秒
  • 設置了 FloatEvaluator 估值器 (這裏是爲了源碼解析,因此特地加上去;若是正常狀況下,該場景是不須要設置估值器的)
  • 設置了 更新回調器
  • 設置了 生命週期監聽器
/** * @author Jiang zinc * @date 建立時間:2019/1/14 * @description 屬性動畫源碼分析 */
public class SimpleAnimationActivity extends Activity {

    private static final String TAG = "SimpleAnimationActivity";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ZincView view = new ZincView(this);
        ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "zinc", 0f, 2f, 5f);
        // 時長
        objectAnimator.setDuration(2000);
        // 插值器
        objectAnimator.setInterpolator(new TimeInterpolator() {
            @Override
            public float getInterpolation(float input) {
                return input;
            }
        });
        // 估值器
        objectAnimator.setEvaluator(new FloatEvaluator());
        // 更新回調
        objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Log.i(TAG, "onAnimationUpdate: " + animation.getAnimatedValue().getClass());
                Log.i(TAG, "onAnimationUpdate: " + animation.getAnimatedValue());
            }
        });
        // 生命週期監聽器
        objectAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                Log.i(TAG, "onAnimationStart: ");
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                Log.i(TAG, "onAnimationEnd: ");
            }

            @Override
            public void onAnimationCancel(Animator animation) {
                Log.i(TAG, "onAnimationCancel: ");
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
                Log.i(TAG, "onAnimationRepeat: ");
            }
        });
        // 開啓
        objectAnimator.start();
    }

    public static class ZincView extends View {
        public ZincView(Context context) {
            super(context);
        }
        public void setZinc(float value) {
            Log.i(TAG, "setZinc: " + value);
        }
    }

}
複製代碼

接下來咱們便 一行行代碼的進入源碼的世界 ,瞭解 「屬性動畫」 的背後祕密。請各位同窗打開本身的源碼查看器,或是 Android Studio,一邊跟着小盆友的思路走,一邊在源碼間跳躍,纔不容易懵。

這裏再次強調,如下代碼分析都是基於這個場景,因此參數的講解和邏輯貫穿也會直接帶入場景中的數值或對象,且再也不作特殊說明。而且以 "FLAG(數字)" 來做爲錨,方便代碼折回講解,各位童鞋可使用瀏覽器搜索功能迅速定位。

咱們的源碼版本是26,接下來就開始咱們的每行分析。

二、第一行代碼

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "zinc", 0f, 2f, 5f);
複製代碼

進入 ObjectAnimator 的 ofFloat 方法

// ObjectAnimator類
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
    // 初始化 ObjectAnimator,將 目標對象 和 屬性 進行關聯
    ObjectAnimator anim = new ObjectAnimator(target, propertyName);
    // 設置關鍵幀 FLAG(1)
    anim.setFloatValues(values);
    return anim;
}
複製代碼

上面👆代碼中的第一行,即是構建一個 ObjectAnimator 實例,同時將 目標對象( ZincView )和 屬性方法(zinc)一同傳入,具體的內容以下👇代碼,咱們還須要再進入一層瞭解

// ObjectAnimator類
private ObjectAnimator(Object target, String propertyName) {
    setTarget(target);
    // FLAG(2)
    setPropertyName(propertyName);
}
複製代碼

再進入上面👆第一行代碼,便來到如下👇的代碼,該方法主要功能是 中止以前目標對象的動畫(若是有以前目標對象的話),而後將目標對象置換爲如今的ZincView對象

// ObjectAnimator類
public void setTarget(@Nullable Object target) {
    // 獲取以前的目標對象,這裏的場景 原來的目標對象 爲空
    final Object oldTarget = getTarget();
    // 兩個目標對象不一樣,由於 oldTarget 爲 null,target 爲 ZincView,進入此if分支
    if (oldTarget != target) {

        // 若是已是在運行,則進行取消
        // isStarted()方法具體請看下面代碼段,只是獲取 mStarted 標記位,
        // 該標記初始化爲false,動畫開啓開始以前,該標記一直爲false,開啓以後,被置爲true,稍後會提到
        if (isStarted()) {
        	// FLAG(24)
            cancel();
        }

        // 進行設置 新的目標對象,進行弱引用,防止內存泄漏
        mTarget = target == null ? null : new WeakReference<Object>(target);

        // 將 初始狀態置爲 false
        mInitialized = false;
    }
}

// ValueAnimator類(ObjectAnimator 繼承至 ValueAnimator)
public boolean isStarted() {
	// mStarted 被初始化爲 false,動畫未開始,該標記爲則爲false
    return mStarted;
}
複製代碼

弱引用與內存泄漏 方面有興趣的同窗,能夠閱讀小盆友的另外一片博客,內存泄漏與排查流程

跳出 setTarget 方法,咱們進入到 setPropertyName 方法,即 FLAG(2) ,能夠看到如下👇代碼,這段代碼其實就是將 屬性方法名(zinc)存至 類成員屬性 mPropertyName中,沒有其餘的有意義操做

// ObjectAnimator類
public void setPropertyName(@NonNull String propertyName) {
	// 此場景中,mValues爲空,此 if 分支不會進入,能夠先不進行理會
	// 至於 mValues 是什麼,在何時會初始化,很快就會揭曉
    if (mValues != null) {
        PropertyValuesHolder valuesHolder = mValues[0];
        String oldName = valuesHolder.getPropertyName();
        valuesHolder.setPropertyName(propertyName);
        mValuesMap.remove(oldName);
        mValuesMap.put(propertyName, valuesHolder);
    }
    // 將 屬性方法名(zinc) 存至 mPropertyName屬性 中
    mPropertyName = propertyName;
    mInitialized = false;
}
複製代碼

小結一下

至此 ObjectAnimator 的構造方法走完,咱們先來小結一下,作了兩件事:

  1. 目標對象ZincView弱引用的形式保存在 mTarget屬性
  2. 屬性方法名zinc 保存在 mPropertyName屬性

看完構造方法中的祕密,回到 ObjectAnimator 的 ofFloat方法 中,進入接下來的那行代碼,即 FLAG(1)這行代碼用於關鍵幀設置,具體以下👇,由於 mValues 此時爲空,且 mProperty 也爲空,因此最終進入 setValues 那一行代碼(即FLAG(3))

// ObjectAnimator類
public void setFloatValues(float... values) {
    // mValues 爲空
    if (mValues == null || mValues.length == 0) {
        // mProperty 爲空,在這個場景中,進入else分支
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofFloat(mProperty, values));
        } else {
            // 進行 PropertyValuesHolder 包裝
            // 將 關鍵幀 進行各自的封裝成 Keyframe
            // 而後打包成 KeyframeSet 與 mPropertyName 共同保存進 PropertyValuesHolder中
            // FLAG(3)
            setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
        }
    } else {
        super.setFloatValues(values);
    }
}
複製代碼

setValues 那一行代碼中其實還包含了一句 PropertyValuesHolder 的構建語句,咱們先進入PropertyValuesHolderofFloat 方法中,能看到以下代碼段,實例化了一個 FloatPropertyValuesHolder 類型的對象,同時又將 屬性方法名 和 關鍵幀的值 傳入。

// PropertyValuesHolder類
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
    return new FloatPropertyValuesHolder(propertyName, values);
}
複製代碼

進入到構造方法,能夠看到以下兩行代碼,第一行是調用了父類的構造方法,而 FloatPropertyValuesHolder 繼承至 PropertyValuesHolder,因此便來到了 PropertyValuesHolder 的構造方法,能夠看到就是將 屬性方法名zinc 存至 mPropertyName屬性 中。

// FloatPropertyValuesHolder類
public FloatPropertyValuesHolder(String propertyName, float... values) {
    super(propertyName);
    // FLAG(4)
    setFloatValues(values);
}

// PropertyValuesHolder類
private PropertyValuesHolder(String propertyName) {
    mPropertyName = propertyName;
}
複製代碼

往下運行,進入 setFloatValues 方法,即 FLAG(4) ,便來到了下面這段代碼,咱們先直接進入 父類的 setFloatValues 方法,這個方法中,主要將關鍵幀的類型存至 mValueType 屬性中,而後進行建立關鍵幀集合存放至 mKeyframes 屬性中。

// FloatPropertyValuesHolder類
public void setFloatValues(float... values) {
    // 保存 關鍵幀
    super.setFloatValues(values);
    // mKeyframes 已經在 super.setFloatValues(values); 中初始化完畢
    // 這裏將其強轉爲 Keyframes.FloatKeyframes 浮點數類型的關鍵幀
    // FLAG(5)
    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}

// PropertyValuesHolder類
public void setFloatValues(float... values) {
    // 保存關鍵幀類型爲 float類型
    mValueType = float.class;
    // 進行拼湊 關鍵幀集合,最後將其返回
    mKeyframes = KeyframeSet.ofFloat(values);
}
複製代碼

進入 KeyframeSetofFloat 方法(具體代碼看下面👇),ofFloat方法主要是將咱們傳進來的關鍵幀數值轉換爲關鍵幀對象,而後封裝成 FloatKeyframeSet類型的關鍵幀集合,方便後期動畫運行時使用(如何使用,在講解start時會詳細講解)。

// KeyframeSet類
/** * 建立 關鍵幀集合 對象 * 傳進來的 "0f, 2f, 5f" 每一個數值將被封裝成 Keyframe 關鍵幀對象 * 最終被放置 FloatKeyframeSet關鍵幀集合中 向上轉型爲 KeyframeSet * * @param values 傳進來的 0f, 2f, 5f 數值 * @return 返回關鍵幀集合 */
public static KeyframeSet ofFloat(float... values) {
	// 是否爲壞數據標記 (not a number) 
    boolean badValue = false;

    // 關鍵幀數量,這裏長度爲 3
    int numKeyframes = values.length;
    // 保證關鍵幀的數組長度至少爲 2
    FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes, 2)];

    // 當關鍵幀數量爲1時,須要補足 兩個,該場景中,進入 else 分支
    if (numKeyframes == 1) {
        // 第一個用 空value 進行補充,默認會爲0
        // 由於 Keyframe 的 mValue屬性類型爲float,jvm會自動爲其填充爲 0
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
        // 填充 第二幀 爲傳進來的數值
        keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);

        // 是否爲 not a number,若是是則改變標記位
        if (Float.isNaN(values[0])) {
            badValue = true;
        }
    } else {

        // 關鍵幀 進行添加進集合
        // 傳進來的值:0f ------> fraction: 0 keyframes[0]
        // 傳進來的值:2f ------> fraction: 1/2 keyframes[1]
        // 傳進來的值:5f ------> fraction: 1 keyframes[2]
        // fraction 爲ofFloat的第一個參數, value 爲ofFloat的第二個參數;
        keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
        for (int i = 1; i < numKeyframes; ++i) {
            keyframes[i] =
                    (FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
            
            // 是否爲 not a number,若是是則改變標記位
            if (Float.isNaN(values[i])) {
                badValue = true;
            }
        }
    }
    // 若是爲 not a number,則進行提示
    if (badValue) {
        Log.w("Animator", "Bad value (NaN) in float animator");
    }
    // 將建立好的 關鍵幀數組keyframes 包裝成 FloatKeyframeSet類型 對象
    // 這樣封裝的好處是 動畫運行時更好的調用當前的關鍵幀
    return new FloatKeyframeSet(keyframes);
}
複製代碼

FloatKeyframeSet是如何封裝的呢?咱們繼續進入查看,進入 FloatKeyframeSet類(看到下面👇這段代碼),發現直接是調用父類的構造方法,而且將入參一塊兒往父類拋,因此咱們進入父類 KeyframeSet 的構造方法。這裏須要先說明下這幾個類的繼承關係,後面也會用到,注意看清這幾個類和接口的名字。

graph LR
    A[FloatKeyframeSet] --> B[KeyframeSet] 
    B[KeyframeSet] --> C[Keyframes]
    A[FloatKeyframeSet] -.-> D[Keyframes.FloatKeyframes] 
複製代碼
graph LR
    A[FloatKeyframe] --> B[Keyframe] 
複製代碼

進入 KeyframeSet 的構造函數,由於 FloatKeyframe 繼承自 Keyframe,因此進入父類後,也就自動向上轉型了。在這構造方法中,只是進行保存一些關鍵幀集合的狀態,例如:關鍵幀集合的長度,將關鍵幀數組轉換爲列表等(具體看下面代碼註釋

// FloatKeyframeSet 類
public FloatKeyframeSet(FloatKeyframe... keyframes) {
    super(keyframes);
}

// KeyframeSet 類
public KeyframeSet(Keyframe... keyframes) {
    // 保存 關鍵幀數量
    mNumKeyframes = keyframes.length;
    // 將 關鍵幀數組轉 換爲 不可變列表
    mKeyframes = Arrays.asList(keyframes);
    // 保存 第一個 關鍵幀
    mFirstKeyframe = keyframes[0];
    // 保存 最後一個 關鍵幀
    mLastKeyframe = keyframes[mNumKeyframes - 1];
    // 保存最後一個關鍵幀的插值器 在這場景中,這裏的插值器爲null
    mInterpolator = mLastKeyframe.getInterpolator();
}
複製代碼

終於走到頭了,咱們須要折回去,到 FLAG(5),這裏在粘貼出來一次(請看下面👇),運行接下來的一行代碼,只是將 父類中 mKeyframes 屬性從 Keyframes類型 強轉爲 FloatKeyframes 後保存在 mFloatKeyframes 屬性中。

// FloatPropertyValuesHolder 類
public void setFloatValues(float... values) {
    // 剛纔一直是在講解這一行代碼的內容
    super.setFloatValues(values);
    // 這裏是講解這一句啦😄 
    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
複製代碼

小結一下

到這裏 PropertyValuesHolder.ofFloat 的代碼內容就走完了,咱們再來小結一下

  1. PropertyValuesHolder 是一個用於裝載 關鍵幀集合屬性動畫名 的數據模型。
  2. 關鍵幀會被一個個存放在 Keyframe 類中,既 有多少個關鍵幀有多少個Keyframe
  3. Keyframe的fraction爲 i / 關鍵幀的數量-1(i>=1),value 則爲對應的關鍵幀數值。即將1進行等分爲(關鍵幀數量-2)份,頭尾兩幀fraction則分別爲0和1,中間幀則按順序各佔一份。

咱們須要再折到 FLAG(3),進行查看 setValues 這一句是如何保存剛剛建立的 PropertyValuesHolder 對象。進入setValues,能夠看到下面👇這段代碼,

/** * 將 PropertyValuesHolder組 進行保存。分別存於: * 一、mValues ---------- PropertyValuesHolder組 * 二、mValuesMap ------- key = PropertyName屬性名,value = PropertyValuesHolder * <p> * 值得注意: * PropertyValuesHolder 中已經存有 關鍵幀 */
public void setValues(PropertyValuesHolder... values) {
	// 保存 PropertyValuesHolder 的長度,該場景長度爲1
    int numValues = values.length;
    // 保存值
    mValues = values;
    mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues);

	// 該場景中,這裏只循環一次。由於 PropertyValuesHolder 只有一個
    for (int i = 0; i < numValues; ++i) {
        PropertyValuesHolder valuesHolder = values[i];
        // 以 key爲屬性方法名zinc ----> value爲對應的PropertyValuesHolder 保存到map中
        mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder);
    }

    mInitialized = false;
}
複製代碼

至此,第一行代碼就已經解析完畢,主要是進行 目標對象,屬性方法名 和 關鍵幀 的包裝和保存。已經在每小段代碼後進行小結,這裏就再也不作冗餘操做。

三、第二行代碼

// 時長
objectAnimator.setDuration(2000);
複製代碼

來到第二行,進行設置動畫時長,進入 setDuration,能夠看到👇下面這段代碼,是調用的父類ValueAnimator的setDuration方法,父類的setDuration方法進行值合法判斷,而後保存至 mDuration 屬性中。

// ObjectAnimator 類
public ObjectAnimator setDuration(long duration) {
    super.setDuration(duration);
    return this;
}

// ValueAnimator 類
private long mDuration = 300;

// ValueAnimator 類
public ValueAnimator setDuration(long duration) {
    // 若是爲負數,拋異常
    if (duration < 0) {
        throw new IllegalArgumentException("Animators cannot have negative duration: " +
                duration);
    }

    // 保存時長
    mDuration = duration;
    return this;
}
複製代碼

值得注意

敲黑板啦,童鞋們注意到沒,mDuration的初始值爲300,也就是說,若是咱們不進行動畫時長的設置,動畫時長就默認爲300毫秒。

四、第三行代碼

// 插值器
objectAnimator.setInterpolator(new TimeInterpolator() {
    @Override
    public float getInterpolation(float input) {
        return input;
    }
});
複製代碼

接下來來到第三行設置插值器代碼,進入後是直接來到 ValueAnimator 的 setInterpolator 方法,也就是說 ObjectAnimator 沒有進行重寫該方法。若是傳入 setInterpolator 方法的參數爲 null,則會默認提供 LinearInterpolator 插值器;若是傳入了非空插值器,則保存至 mInterpolator 屬性中。

// ValueAnimator 類
private static final TimeInterpolator sDefaultInterpolator =
            new AccelerateDecelerateInterpolator();

// ValueAnimator 類
private TimeInterpolator mInterpolator = sDefaultInterpolator;

// ValueAnimator 類
public void setInterpolator(TimeInterpolator value) {
    if (value != null) {
        mInterpolator = value;
    } else {
        mInterpolator = new LinearInterpolator();
    }
}
複製代碼

值得注意

當咱們沒有進行設置插值器時,默認的爲咱們初始化了 AccelerateDecelerateInterpolator 插值器,該插值器的走勢以下動態圖。

五、第四行代碼

objectAnimator.setEvaluator(new FloatEvaluator());
複製代碼

第四行代碼用於設置估值器,進入源碼,這裏一樣也是進入到父類 ValueAnimator 的 setEvaluator 方法,ObjectAnimator 沒有進行重寫。估值器傳入後,會對其進行合法性驗證,例如:估值器非空,mValues非空且長度大於零。若是合法性驗證不經過,則直接忽略傳入的估值器。注意哦(再敲黑板!)估值器沒有進行默認值的設置,至於他是如何正常運轉的,其實咱們在前面講 「估值器定義」 一小節中就已經提到,但未深刻探討,固然這裏也同樣先不探討,還未到時候,在 「第七行代碼」 中會進行說明。

// ValueAnimator 類
PropertyValuesHolder[] mValues;

// ValueAnimator 類
public void setEvaluator(TypeEvaluator value) {
    if (value != null && mValues != null && mValues.length > 0) {
        mValues[0].setEvaluator(value);
    }
}
複製代碼

值得注意

咱們設置的估值器只會做用於 mValues 的第一項,可是咱們這個場景中,也就只有一個元素。

mValues 是什麼?咱們在 「第一行代碼」 中就已經初始化完畢啦,忘記的童鞋往回看看😄。看源碼就是來來回回看,耐得住性子,才能更加牛x。

六、第五行代碼

// 更新回調
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Log.i(TAG, "onAnimationUpdate: " + animation.getAnimatedValue().getClass());
        Log.i(TAG, "onAnimationUpdate: " + animation.getAnimatedValue());
    }
});
複製代碼

第五行代碼用於設置更新回調,進入源碼,來到下面這段代碼,一樣仍是直接來到 其父類 ValueAnimator 中,這裏就比較簡單了,若是 mUpdateListeners 屬性未初始化,就建立一個列表,而後將更新監聽器添加入列表。

// ValueAnimator 類
ArrayList<AnimatorUpdateListener> mUpdateListeners = null;

// ValueAnimator 類
public void addUpdateListener(AnimatorUpdateListener listener) {
    if (mUpdateListeners == null) {
        mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
    }
    mUpdateListeners.add(listener);
}
複製代碼

值得注意

不知道童鞋們有沒有和小盆友同樣的錯覺,一直覺得 AnimatorUpdateListenerAnimator.AnimatorListener(下一小節講)各自只能設置一個,若是屢次設置是會覆蓋。看了源碼才得知是有序列表持有。只怪本身以前太單純😂。

七、第六行代碼

// 生命週期監聽器
objectAnimator.addListener(new Animator.AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        Log.i(TAG, "onAnimationStart: ");
    }

    @Override
    public void onAnimationEnd(Animator animation) {
        Log.i(TAG, "onAnimationEnd: ");
    }

    @Override
    public void onAnimationCancel(Animator animation) {
        Log.i(TAG, "onAnimationCancel: ");
    }

    @Override
    public void onAnimationRepeat(Animator animation) {
        Log.i(TAG, "onAnimationRepeat: ");
    }
});
複製代碼

第六行代碼進行設置生命週期監聽器,進入源碼,此次是來到 ObjectAnimator 的爺爺類Animator 的 addListener 方法。

這裏有必要給出這幾個類的繼承關係和實現的接口,請記住他,由於在 「第七行代碼」 中會提到。這裏立個FLAG(6),須要時咱們再折回來看看。

graph LR
    A[ObjectAnimator] --> B[ValueAnimator] 
    B[ValueAnimator]  --> C[Animator] 
    B[ValueAnimator] -.-> D[AnimationHandler.AnimationFrameCallback]
複製代碼

這裏的邏輯和 「第五行的代碼」 的源碼邏輯能夠說是同樣的,都是先判空,若是爲空,則進行實例化一個有序列表,而後將監聽器放入列表中。

// Animator 類
ArrayList<AnimatorListener> mListeners = null;

// Animator 類
public void addListener(AnimatorListener listener) {
    if (mListeners == null) {
        mListeners = new ArrayList<AnimatorListener>();
    }
    mListeners.add(listener);
}
複製代碼

八、第七行代碼

// 開啓
objectAnimator.start();
複製代碼

來到了最後的第七行代碼,這行代碼雖然簡短,但卻蘊含着最多的祕密,讓咱們來一點點揭開。進入start方法,看到以下代碼。

// ObjectAnimator 類
public void start() {
	// FLAG(7)
    AnimationHandler.getInstance().autoCancelBasedOn(this);
    if (DBG) {
    	// 省略,用於調試時的日誌輸出,DBG 是被定義爲 靜態不可修改的 false,因此能夠忽略這個分支
    	......
    }
    // FLAG(8)
    super.start();
}
複製代碼

咱們先進入第一行代碼 的 第一小段,即 getInstance()。看到以下代碼,其實看到getInstance這個方法名咱們應該就會想到一個設計模式——單例模式,經過方法內的代碼也確實驗證了這個猜測。可是有些許不一樣的是單例對象放在 ThreadLocal 中,用於確保的是 線程單例,而非進程中全局單例,換句話說,不一樣線程的AnimationHandler對象是不相同的

// AnimationHandler 類
/** * 保證 AnimationHandler 當前線程單例 */
public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();

// AnimationHandler 類
/** * 獲取 AnimationHandler 線程單例 */
public static AnimationHandler getInstance() {
    if (sAnimatorHandler.get() == null) {
        sAnimatorHandler.set(new AnimationHandler());
    }
    return sAnimatorHandler.get();
}
複製代碼

通過上面代碼,咱們便獲取到了 AnimationHandler 對象。AnimationHandler 是一個用於接受脈衝(即 垂直同步信號),讓同一線程的動畫使用的計算時間是相同的,這樣的做用是讓同步動畫成爲可能。 至於 AnimationHandler 是如何接受垂直同步信號,咱們繼續賣關子,稍後就會知道。

咱們折回到 FLAG(7),看第二小段代碼,具體代碼以下,這裏的代碼其實在咱們設定的場景中是不會運行的,由於 mAnimationCallbacks 此時長度還爲0。但咱們仍是進行深刻的分析,具體的每行代碼講解請看註釋,總結起來就是 若是 mAnimationCallbacks列表中的元素 和 參數objectAnimator對象 存在相同的目標對象和相同的PropertyValuesHolder,則將mAnimationCallbacks列表中對應的元素進行取消操做。

// AnimationHandler 類
/** * AnimationFrameCallback 此場景中就是 咱們第一行代碼實例化的 ObjectAnimator 對象, * 由於 ObjectAnimator 的父類實現了 AnimationFrameCallback 接口,具體繼承關係能夠看 FLAG(6) 處的類圖 */
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
        new ArrayList<>();

// AnimationHandler 類
void autoCancelBasedOn(ObjectAnimator objectAnimator) {
	// 場景中,mAnimationCallback 此時長度爲0,因此其實此循環不會進入
    for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
        AnimationFrameCallback cb = mAnimationCallbacks.get(i);
        if (cb == null) {
            continue;
        }
        // 將 相同的目標對象 且 PropertyValuesHolder徹底同樣的動畫進行取消操做
        // 取消操做在 「第一行代碼」 便已經詳細闡述,這裏就再也不贅述
        if (objectAnimator.shouldAutoCancel(cb)) {
            ((Animator) mAnimationCallbacks.get(i)).cancel();
        }
    }
}

// ObjectAnimator 類
/** * 是否能夠 進行取消 */
boolean shouldAutoCancel(AnimationHandler.AnimationFrameCallback anim) {
    // 爲空,則返回
    if (anim == null) {
        return false;
    }

    if (anim instanceof ObjectAnimator) {
        ObjectAnimator objAnim = (ObjectAnimator) anim;
        // 該動畫能夠自動取消 且 當前的對象和anim 的所持的目標對象和PropertyValuesHolder同樣
        // 則能夠 進行取消,返回true
        if (objAnim.mAutoCancel && hasSameTargetAndProperties(objAnim)) {
            return true;
        }
    }
    // 不然不取消
    return false;
}

// ObjectAnimator 類
/** * ObjectAnimator 是否有相同的 目標對象target 和 PropertyValuesHolder * PropertyValuesHolder 的初始化在第一行代碼已經詳細講述,忘記的童鞋折回去再👀看一遍 */
private boolean hasSameTargetAndProperties(@Nullable Animator anim) {
    if (anim instanceof ObjectAnimator) {
    	// 獲取 PropertyValuesHolder 
        PropertyValuesHolder[] theirValues = ((ObjectAnimator) anim).getValues();
        // 目標對象相同 且 PropertyValuesHolder長度相同
        if (((ObjectAnimator) anim).getTarget() == getTarget() &&
                mValues.length == theirValues.length) {
			// 循環檢測 PropertyValuesHolder 中 屬性名是否 「徹底相同」,只要有一個不一樣 則返回false
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvhMine = mValues[i];
                PropertyValuesHolder pvhTheirs = theirValues[i];
                if (pvhMine.getPropertyName() == null ||
                        !pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
                    return false;
                }
            }
            // 所有相同,返回true
            return true;
        }
    }
    // 不是 ObjectAnimator 直接返回false
    return false;
}
複製代碼

看完首行代碼,咱們來到調用 父類ValueAnimator 的 start 方法這行FLAG(8),進入該方法,具體代碼以下,能夠看到調用了 start() 的重載方法 start(boolean),playBackwards是用於標記是否要反向播放,顯然傳入的爲false,表示正向播放。start方法中作了這幾件事:

  1. 初始化一些屬性,例如運行的狀態標記(注意此處 開始狀態mStarted便置爲true);
  2. 幀和動畫的播放時間則置爲-1;
  3. 添加動畫回調,用於接受 垂直同步信號;
  4. 設置當前的播放 fraction;
// ValueAnimator 類
public void start() {
    start(false);
}

// ValueAnimator 類
/** * @param playBackwards ValueAnimator 是否應該開始反向播放。 */
private void start(boolean playBackwards) {
    // 必需要在有 looper 的線程中運行
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }

    // 是否反向
    mReversing = playBackwards;
    // 是否接受脈衝,mSuppressSelfPulseRequested初始化爲false,因此這裏爲true,表示接受脈衝
    mSelfPulse = !mSuppressSelfPulseRequested;

    // 此處 playBackwards 爲false,該分支不理會,處理反向播放
    if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
        if (mRepeatCount == INFINITE) {
            float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
            mSeekFraction = 1 - fraction;
        } else {
            mSeekFraction = 1 + mRepeatCount - mSeekFraction;
        }
    }

    // 將開始狀態(mStarted)置爲true
    // 暫停狀態(mPaused)置爲false
    // 運行狀態(mRunning)置爲false
    // 是否終止動畫狀態(mAnimationEndRequested)置爲false
    mStarted = true;
    mPaused = false;
    mRunning = false;
    mAnimationEndRequested = false;

    // 重置 mLastFrameTime,這樣若是動畫正在運行,調用 start() 會將動畫置於已啓動但還沒有到達的第一幀階段。
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;

    // 添加動畫回調,用於接受 垂直同步信號
    addAnimationCallback(0);

    // 此場景中,mStartDelay爲0,因此進入分支
    if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
		// FLAG(14)
        startAnimation();

        // 設置 mSeekFraction,這個屬性是經過 setCurrentPlayTime() 進行設置
        if (mSeekFraction == -1) {
            // FLAG(16)
            setCurrentPlayTime(0);
        } else {
            setCurrentFraction(mSeekFraction);
        }
    }
}
複製代碼

屬性的初始化和各自意義,咱們就不單獨講解,使用到的時候天然就能體會到他的存在乎義。因此咱們直接進入到第三步,即添加動畫回調addAnimationCallback的代碼。

這裏進行判斷是否要接受脈衝,咱們上面的代碼已經將 mSelfPulse設置爲true,表示須要接受脈衝,因此不進入if分支,來到下一行代碼,是否是很熟悉?這裏獲取的即是咱們上面已經初始化的 AnimationHandler,這裏調用了 AnimationHandleraddAnimationFrameCallback,同時把 本身this 和 延時delay(這裏爲0)一同帶入。

// ValueAnimator 類
private void addAnimationCallback(long delay) {
	// 若是不接受脈衝,則不會添加回調,這樣天然就中斷了脈衝帶來的更新
	// 在 start 方法中已經設置爲 true,因此不進入if分支
    if (!mSelfPulse) {
        return;
    }
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}

// ValueAnimator 類
public AnimationHandler getAnimationHandler() {
    return AnimationHandler.getInstance();
}
複製代碼

這樣咱們便來到了 AnimationHandleraddAnimationFrameCallback 方法,根據該方法的官方註釋可知,註冊的callback會在下一幀調用,但須要延時指定的delay以後,但是咱們這裏的delay爲0,因此在咱們這場景中能夠進行忽略,減小干擾因素。

來到第一行,由於 mAnimationCallbacks 此時長度爲0,因此進入該if分支。 咱們須要先進入 getProvider() 方法,待會再折回來,往下看

/** * Register to get a callback on the next frame after the delay. * 註冊回調,可讓下一幀進行回調。 */
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    /** * 第一次進來的時候 mAnimationCallbacks 是空的, * 因此會向 {@link MyFrameCallbackProvider#mChoreographer} 提交一次回調。 */
    if (mAnimationCallbacks.size() == 0) {
    	// FLAG(9)
        getProvider().postFrameCallback(mFrameCallback);
    }

    /** * 此處的 callback 即爲 ValueAnimator 和 ObjectAnimator * 由於 ObjectAnimator 繼承於 ValueAnimator,ValueAnimator 實現了 AnimationFrameCallback 接口 * 這裏 callback 從 {@link ValueAnimator#start()} 傳進來,使用了 this */
    // FLAG(13)
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }

    // 記錄延時的回調 和 延時的時間
    if (delay > 0) {
        mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
    }
}
複製代碼

來到 getProvider 方法,這裏初始化一個 MyFrameCallbackProvider 對象,他負責與 Choreographer 進行交互。

這裏值得一提的是 MyFrameCallbackProvider 實現了 AnimationFrameCallbackProvider 接口(關係以下圖所示),而AnimationHandler中,提供定時的幀回調,並非規定必定要經過 Choreographer 來接收垂直同步來達到效果,也能夠本身行實現 AnimationFrameCallbackProvider 接口,自行提供不一樣的定時脈衝來實現效果,來頂替這裏的 MyFrameCallbackProviderAnimationHandler 同時也提供了 setProvider 方法來進行設置該 AnimationFrameCallbackProvider 類。

graph LR
    A[MyFrameCallbackProvider] -.-> B[AnimationFrameCallbackProvider] 
複製代碼
// AnimationHandler 類
private AnimationFrameCallbackProvider getProvider() {
    if (mProvider == null) {
        mProvider = new MyFrameCallbackProvider();
    }
    return mProvider;
}

// AnimationHandler 類
/** * 使用 Choreographer 提供定時脈衝 進行幀回調 */
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {

    final Choreographer mChoreographer = Choreographer.getInstance();

	// 省略 接口的實現
	......
}
複製代碼

話說回來 Choreographer 是什麼?

Choreographer 是用於接收定時脈衝(例如 垂直同步),協調 「動畫、輸入、繪製」 時機的類。 咱們這裏不展開闡述 Choreographer 內部的運起色制,可是咱們必須知道的是,Android手機每秒會有60幀的回調,即約16.66毫秒便會調用一次 Choreographer 中的 類型爲FrameDisplayEventReceiver的mDisplayEventReceiver屬性 中的 onVsync 方法。後續還會繼續用到這裏的知識點(敲黑板了,要考的),來說解屬性動畫是怎麼動起來的,咱們先打個標記FLAG(10)。

咱們先折回 FLAG(9),看後半段 postFrameCallback 作了什麼操做,進入代碼,具體以下,這裏作了一件很重要的事,就是 註冊進Choreographer,接收垂直同步信號Choreographer 中屢次重載了 postFrameCallbackDelayed 方法,最終在FLAG(10)處,將咱們從 MyFrameCallbackProvider 傳入的 callback 保存在了 ChoreographermCallbackQueues 中,這裏須要在打一個標記FLAG(11),後續須要再用到。

// AnimationHandler$MyFrameCallbackProvider 類
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
    mChoreographer.postFrameCallback(callback);
}

// Choreographer 類
public void postFrameCallback(FrameCallback callback) {
    postFrameCallbackDelayed(callback, 0);
}

// Choreographer 類
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    if (callback == null) {
        throw new IllegalArgumentException("callback must not be null");
    }
    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

// Choreographer 類
private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {
    if (DEBUG_FRAMES) {
    	// 調試時,日誌輸出
    	......
    }

    synchronized (mLock) {
        final long now = SystemClock.uptimeMillis();
        final long dueTime = now + delayMillis;
        /** * 此處將 {@link FrameCallback} 添加到對應的回調隊列中 * FLAG(10) */
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

        if (dueTime <= now) {
            scheduleFrameLocked(now);
        } else {
            Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
            msg.arg1 = callbackType;
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, dueTime);
        }
    }
}
複製代碼

咱們須要再次折回 FLAG(9),須要說明下傳入的參數 mFrameCallback, 實現了 Choreographer.FrameCallback 接口,這裏面會調用 doAnimationFrame 方法,這個先不展開,待會講到幀回調時,在具體剖析。先來到下面的if分支,用於將本身(mFrameCallback)再次添加進 Choreographer,運行的邏輯和上面剛剛闡述的邏輯是如出一轍。爲何還要再添加一次呢?這是由於添加進的回調,在每次被調用後就會被移除,若是還想繼續接收到垂直信號,則須要將本身再次添加。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
    	// FLAG(12)
    	// 這裏後面講
        doAnimationFrame(getProvider().getFrameTime());

		/** * 再次將本身添加進脈衝回調中 * 由於 {@link Choreographer#postFrameCallback(Choreographer.FrameCallback)} 每調用一次 * 就會將添加的回調移除 */
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);
        }
    }
};
複製代碼

折回到FLAG(13),就是下面這段代碼,作了很普通的一件事,就是把咱們在 「第一行代碼」 實例化的 ObjectAnimator 對象存至 mAnimationCallbacks 回調列表中。接下去的分支,咱們這場景中不需理會,由於咱們不作延時操做。

/** * 此處的 callback 即爲 ValueAnimator 和 ObjectAnimator * 由於 ObjectAnimator 繼承於 ValueAnimator,ValueAnimator 實現了 AnimationFrameCallback 接口 * 這裏 callback 從 {@link ValueAnimator#start()} 傳進來,使用了 this */
if (!mAnimationCallbacks.contains(callback)) {
    mAnimationCallbacks.add(callback);
}

if (delay > 0) {
    mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
複製代碼

咱們須要折回到FLAG(14),進入到 startAnimation 方法中,具體代碼以下,這個方法作了以下幾個步驟:

  1. 初始化動畫,具體是設置關鍵幀集合的估值器;
  2. 將運行狀態置爲true;
  3. 設置 mOverallFraction(這個屬性咱們後面用到時在說明是什麼做用);
  4. 回調 監聽器;

接下來咱們分析第一和第四小點

// ValueAnimator 類
private void startAnimation() {
    // 省略跟蹤代碼
    ......
    mAnimationEndRequested = false;
    // 初始化 動畫
    initAnimation();
    // 將運行狀態置爲 true
    mRunning = true;
    
    if (mSeekFraction >= 0) {
        mOverallFraction = mSeekFraction;
    } else {
    	// 跟蹤動畫的 fraction,範圍從0到mRepeatCount + 1
    	// mRepeatCount 爲咱們設置動畫循環次數,咱們這裏沒有設置,則默認爲0,只運行一次
        mOverallFraction = 0f;
    }

	// FLAG(15)
    if (mListeners != null) {
        // 進行開始回調
        notifyStartListeners();
    }
}
複製代碼

先進行分析第一小點,咱們進入 initAnimation 方法,當首次進入時,mInitialized 爲false,因此進入該分支,這裏循環調用了 mValues元素(PropertyValuesHolder類型)init 方法。

// ValueAnimator 類
void initAnimation() {
    // 當首次進入時,mInitialized爲false
    // 初始化 估值器Evaluator
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].init();
        }

        // 將初始化標記 轉爲true,防止屢次初始化
        mInitialized = true;
    }
}
複製代碼

進入到 PropertyValuesHolderinit 方法中,代碼以下,該方法作了一件事,就是初始化 mKeyframes估值器,而這估值器在咱們講述 「第四行代碼」 時,就已經置入到 PropertyValuesHolder 中,這一方法是將這個估值器置入關鍵幀集合中。

// PropertyValuesHolder 類
/** * 初始化 Evaluator 估值器 */
void init() {
    /** * 若是 Evaluator 爲空,則根據 mValueType 類型進行設置,可是也只是提供 * {@link IntEvaluator} 和 {@link FloatEvaluator} * 若是均不是這兩種類型,則爲null */
    if (mEvaluator == null) {
        // We already handle int and float automatically, but not their Object
        // equivalents
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator :
                        null;
    }

    // 若是有估值器,則進行設置
    if (mEvaluator != null) {
        // KeyframeSet knows how to evaluate the common types - only give it a custom
        // evaluator if one has been set on this class
        mKeyframes.setEvaluator(mEvaluator);
    }

}
複製代碼

這裏須要說句題外話,下面下面這段代碼,返回的是false,也就是說👆上面設置估值器的代碼中,當mEvaluator爲空時,若是咱們使用的簡單類型的float,此處的並不會使用默認的sFloatEvaluator,而是仍是爲null。

System.out.println(float.class == Float.class);  // 輸出的是false
複製代碼

接下來進行分析第四小點,折回到FLAG(15),此時 mListeners 已經在 「第六行代碼」 時就初始化,並添加了一個監聽器,因此會進入該if分支,進入 notifyStartListeners 方法,具體代碼以下,這裏便回調到了咱們 「第六行代碼」 設置的生命週期監聽器的 onAnimationStart 方法中,同時將自身 ObjectAnimator 對象做爲參數帶出。

// ValueAnimator 類
private void notifyStartListeners() {
    // 有 回調監聽器,且從未回調
    if (mListeners != null && !mStartListenersCalled) {
        ArrayList<AnimatorListener> tmpListeners =
                (ArrayList<AnimatorListener>) mListeners.clone();
        int numListeners = tmpListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            /** * 進行回調開始,這裏便 回調到 咱們設置 * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator)} * 的方法中 */
            tmpListeners.get(i).onAnimationStart(this, mReversing);
        }
    }

    mStartListenersCalled = true;
}

// Animator$AnimatorListener 類
default void onAnimationStart(Animator animation, boolean isReverse) {
   onAnimationStart(animation);
}
複製代碼

這裏一路調用進來,算是比較深遠了,但無大礙,咱們回到FLAG(16),繼續看 start 方法的最後一行代碼 setCurrentPlayTime(0) 的具體內容,代碼以下,這方法是爲了讓若是動畫時長小於或等於零時,直接到達動畫的末尾,即fraction置爲1。

// ValueAnimator 類
public void setCurrentPlayTime(long playTime) {
    // 若是設置的 mDuration 爲 2000,playTime 爲 0,則 fraction 爲 0
    // 若是設置的 mDuration 爲 0,則 fraction 爲 1,直接到最後一個關鍵幀
    float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
    setCurrentFraction(fraction);
}
複製代碼

接下來看 setCurrentFraction 作了什麼操做,第一行的 initAnimation 在以前就已經運行過了,因此並不會再次初始化,clampFraction方法是爲了讓 fraction 落在合法的區域內,即 [0,mRepeatCount + 1],這裏再也不展開(篇幅太長了😂)。

來到 if-else,isPulsingInternal方法內判斷的mLastFrameTime 是否大於等於0,可是 mLastFrameTime 到目前爲止仍是 -1,因此進入else分支,將fraction保存至mSeekFraction。

// ValueAnimator 類
public void setCurrentFraction(float fraction) {
    // 初始化 動畫,但這裏其實只是作了 估值器的賦值初始化
    initAnimation();

    // 獲取 合法的fraction
    fraction = clampFraction(fraction);

    mStartTimeCommitted = true;

    // 動畫是否已進入動畫循環
    if (isPulsingInternal()) {
        // 獲取動畫已經使用的時長
        long seekTime = (long) (getScaledDuration() * fraction);
        // 獲取當前動畫時間
        long currentTime = AnimationUtils.currentAnimationTimeMillis();
        // 僅修改動畫運行時的開始時間。 Seek Fraction將確保非運行動畫跳到正確的開始時間。
        mStartTime = currentTime - seekTime;
    } else {
        // 若是動畫循環還沒有開始,或者在開始延遲期間。seekTime,一旦延遲過去,startTime會基於seekTime進行調整。
        mSeekFraction = fraction;
    }

    // 總fraction,攜帶有迭代次數
    mOverallFraction = fraction;

    // 計算當次迭代的 fraction
    final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
    
    // 根據 fraction ,計算出對應的value
    // FLAG(17)
    animateValue(currentIterationFraction);
}
複製代碼

接下來是 getCurrentIterationFraction 方法,這個方法用於獲取當前迭代的 fraction,由於咱們須要知道的是,若是設置了屢次循環播放動畫,即mRepeatCount>0時,則fraction是包含有mRepeatCount次數的。而這個方法就是去除了次數,只剩下當次的進度,即範圍爲 [0,1] 。

// ValueAnimator 類
private float getCurrentIterationFraction(float fraction, boolean inReverse) {
    // 確保 fraction 的範圍在合法範圍 [0,mRepeatCount+1] 中
    fraction = clampFraction(fraction);
    // 當前迭代次數
    int iteration = getCurrentIteration(fraction);
    /** * fraction 是 包含有 mRepeatCount 的值 * iteration 是 迭代的次數 * 二者相減 fraction - iteration 得出的 currentFraction 則爲當前迭代中的 進度 */
    float currentFraction = fraction - iteration;
    // 計算最終當次迭代的 fraction 值,主要是受 inReverse 和 REVERSE 的影響
    return shouldPlayBackward(iteration, inReverse) ? 1f - currentFraction : currentFraction;
}
複製代碼

咱們回到 FLAG(17),進入 animateValue 方法,具體代碼以下。咱們須要明確的是,傳進來的參數是當次的進度,也就是不含循環次數的。

咱們會先進入到 ObjectAnimatoranimateValue,可是源碼會先進入其父類 ValueAnimatoranimateValue。因此咱們先進入父類的 animateValue

看到第一行代碼,你或許就明白了,咱們在 「第三行代碼」 設置的插值器就在這個時候發揮做用了,同時會 將插值器返回的值設置回 fraction,起到改變進度的快慢的做用 。(這便揭開了咱們在 「插值器」 一節中賣的關子)

// ObjectAnimator 類
void animateValue(float fraction) {
    final Object target = getTarget();
    // 不會進入,此時的target在 「第一行代碼」 時就設置了,因此不爲空
    if (mTarget != null && target == null) {
        cancel();
        return;
    }

    // 進入父類 ValueAnimator
    super.animateValue(fraction);
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
    	// FLAG(25)
        mValues[i].setAnimatedValue(target);
    }
}

// ValueAnimator 類
void animateValue(float fraction) {
    // 經過 插值器 進行計算出 fraction
    fraction = mInterpolator.getInterpolation(fraction);

    // 當前次數的進度
    mCurrentFraction = fraction;

    // 循環 全部的 PropertyValuesHolder,進行估值器計算
    int numValues = mValues.length;
    for (int i = 0; i < numValues; ++i) {
        mValues[i].calculateValue(fraction);
    }

    /** * 進行回調 {@link AnimatorUpdateListener#onAnimationUpdate(ValueAnimator)} */
    // FLAG(21)
    if (mUpdateListeners != null) {
        int numListeners = mUpdateListeners.size();
        for (int i = 0; i < numListeners; ++i) {
            mUpdateListeners.get(i).onAnimationUpdate(this);
        }
    }
}
複製代碼

緊接着進行循環調用 mValues 中的元素的 calculateValue 方法(咱們這場景中 mValues 的元素其實只有一個),進入該方法,能夠看到以下代碼。這裏進入的是 PropertyValuesHolder 的子類 FloatPropertyValuesHolder

// FloatPropertyValuesHolder 類
void calculateValue(float fraction) {
	// mFloatKeyframes 在 「第一行代碼」 時就已經初始化
    mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
複製代碼

咱們接着進入 getFloatValue 方法,其具體實現類是 FloatKeyframeSet。具體代碼以下,getFloatValue 中分了幾個狀況,咱們接下來分狀況討論,往下走。

// FloatKeyframeSet 類
/** * 獲取當前 進度數值爲fraction的 value * 落實一個場景: 0f, 2f, 5f * mKeyframes 中存了三個 FloatKeyframe * mNumKeyframes 則爲 3 * * @param fraction The elapsed fraction of the animation * @return */
@Override
public float getFloatValue(float fraction) {
	// FLAG(18)
    if (fraction <= 0f) {
        // 獲取 0f 關鍵幀
        final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
        // 獲取 2f 關鍵幀
        final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
        // 獲取 0f 關鍵幀的值 即 0f
        float prevValue = prevKeyframe.getFloatValue();
        // 獲取 2f 關鍵幀的值 即 1f
        float nextValue = nextKeyframe.getFloatValue();
        // 獲取 0f 關鍵幀的fraction,這裏爲0
        float prevFraction = prevKeyframe.getFraction();
        // 獲取 2f 關鍵幀的fraction,這裏爲1/2
        float nextFraction = nextKeyframe.getFraction();

        // 這裏的插值器爲空,並不會運行該分支
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        
        float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
        return mEvaluator == null ?
                prevValue + intervalFraction * (nextValue - prevValue) :
                ((Number) mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                        floatValue();

    } else if (fraction >= 1f) {	// FLAG(19)
        final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
        final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
        float prevValue = prevKeyframe.getFloatValue();
        float nextValue = nextKeyframe.getFloatValue();
        float prevFraction = prevKeyframe.getFraction();
        float nextFraction = nextKeyframe.getFraction();
        final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
        if (interpolator != null) {
            fraction = interpolator.getInterpolation(fraction);
        }
        float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
        return mEvaluator == null ?
                prevValue + intervalFraction * (nextValue - prevValue) :
                ((Number) mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                        floatValue();
    }

	// FLAG(20)
    // 初始化第一幀, 0f
    FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
    // 從第二幀開始循環
    for (int i = 1; i < mNumKeyframes; ++i) {
        // 相對於prevKeyframe,取下一幀
        FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);

		// 判斷是否落在 該區間
        if (fraction < nextKeyframe.getFraction()) {
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());

            float prevValue = prevKeyframe.getFloatValue();
            float nextValue = nextKeyframe.getFloatValue();
            if (interpolator != null) {
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            // 估值器計算
            return mEvaluator == null ?
                    prevValue + intervalFraction * (nextValue - prevValue) :
                    ((Number) mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            floatValue();
        }
		// 變換前一幀
        prevKeyframe = nextKeyframe;
    }

    // 正常狀況下不該該運行到這
    return ((Number) mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
複製代碼

如下圖片均爲手寫,勿噴😄

狀況一:fraction = 0。進入FLAG(18)

狀況二:fraction = 1/4。進入FLAG(20)

狀況三:fraction = 3/4。進入FLAG(20)

狀況四:fraction = 1。進入FLAG(19)

通過上面四種狀況,咱們能夠知道 intervalFraction 值,即爲 當前幀段的比例數(幀段即爲 0f-2f,2f-5f) 而 返回值 即爲 fraction 經過估值器轉換爲 真實須要的值,即咱們程序員能夠拿來用,例如咱們這裏須要的是 0f-5f的值。

還記得咱們在 「估值器」 一小節中賣的關子麼?狀況二中的公式就是咱們用於計算 此處的返回值,在估值器爲null時。若是估值器不爲null,則按照設置的估值器邏輯計算。

mEvaluator == null ?
				 prevValue + intervalFraction * (nextValue - prevValue) :
				 ((Number) mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
				         floatValue();
複製代碼

你可能會有疑惑,咱們這場景中不是有設置一個 FloatEvaluator 估值器麼?確實是,但FloatEvaluator內部邏輯其實就和咱們估值器爲null時是如出一轍的。具體代碼以下

public class FloatEvaluator implements TypeEvaluator<Number> {
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}
複製代碼

至此咱們獲得了 估值器計算出來的咱們須要的值

咱們折回 FLAG(21),看到 mUpdateListeners 這屬性,童鞋們應該也知道這是在 「第五行代碼」 設置的更新監聽器,進入該分支,會循環着調用更新監聽器的 onAnimationUpdate 方法。這便進入到了咱們設置的更新監聽器的代碼中。

接下來咱們須要折回到父類 ObjectAnimatoranimateValue,即FLAG(25)。會進行循環調用 mValuessetAnimatedValue,而咱們這裏的場景的 mValuesFloatPropertyValuesHolder 類型,因此會調用該類的 setAnimatedValue, 具體代碼以下。而 FLAG(26) 就是將咱們剛纔上面所計算的 mFloatAnimatedValue 值經過 native方法nCallFloatMethod 設置到對應的對象的方法中,也就是咱們此處場景中的 ZincView類的setZinc方法

// FloatPropertyValuesHolder 類
void setAnimatedValue(Object target) {
    if (mFloatProperty != null) {
        mFloatProperty.setValue(target, mFloatAnimatedValue);
        return;
    }
    if (mProperty != null) {
        mProperty.set(target, mFloatAnimatedValue);
        return;
    }
    if (mJniSetter != 0) {
    	// FLAG(26)
        nCallFloatMethod(target, mJniSetter, mFloatAnimatedValue);
        return;
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = mFloatAnimatedValue;
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}
複製代碼

至此,咱們一個流程走完,但並不表明着就已經完成了,由於這裏面還只是第一幀的回調,而後續的幀回調還未闡述,還有動畫的終止還未說清。因此咱們繼續前行,先來 解決後續幀回調問題

還記得咱們在講 Choreographer 時,經過 AnimationHandler 注入了一個回調麼?這個時候後續的幀回調就全靠他了。咱們前面說過每次 「垂直同步」 信號的到來,回調用到 Choreographer$FrameDisplayEventReceiveronVsync 的,而該方法最終會調用到咱們在FLAG(11)放入回調隊列 mCallbackQueues 中的 mFrameCallbackdoFrame 方法。這就回到了咱們FLAG(12)標記的地方。

// AnimationHandler 類
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        mCurrentFrameTime = System.currentTimeMillis();
        doAnimationFrame(mCurrentFrameTime);
        if (mAnimationCallbacks.size() > 0) {
            getProvider().postFrameCallback(this);
        }
    }
};
複製代碼

進入 doAnimationFrame 方法,便看到對 mAnimationCallbacks 進行了遍歷,調用 doAnimationFrame 方法。 而 mAnimationCallbacks 是咱們在講解 addAnimationFrameCallback方法時,就將傳進來的 ObjectAnimator 對象放入其中的。

// AnimationHandler 類
private void doAnimationFrame(long frameTime) {
    long currentTime = SystemClock.uptimeMillis();
    final int size = mAnimationCallbacks.size();
    for (int i = 0; i < size; i++) {
        final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
        if (callback == null) {
            continue;
        }
        // isCallbackDue 方法用於剔除須要延時調用的回調
        // 若是該 callback 是在延時隊列的,而且延時還未完成,不進行回調
        if (isCallbackDue(callback, currentTime)) {
			// 進入這一行
            callback.doAnimationFrame(frameTime);
            if (mCommitCallbacks.contains(callback)) {
                getProvider().postCommitCallback(new Runnable() {
                    @Override
                    public void run() {
                        commitAnimationFrame(callback, getProvider().getFrameTime());
                    }
                });
            }
        }
    }
    cleanUpList();
}
複製代碼

當調用 doAnimationFrame 方法,則來到了下面的這段代碼,該方法主要是對 啓動時間進行容錯處理,而後保證動畫進行啓動,同時在 animateBasedOnTime 方法中進行更新的監聽回調(咱們接下來分析),最後根據 animateBasedOnTime 的返回值,判斷是否動畫已經結束,結束的話進行 動畫生命週期 的回調(待會也會分析)。

// ValueAnimator 類
public final boolean doAnimationFrame(long frameTime) {
	// 初始化第一幀,同時考慮延時
    if (mStartTime < 0) {
        mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
    }

    // 處理 暫停 和 恢復 的狀況,這裏咱們不考慮
    if (mPaused) {
        mPauseTime = frameTime;
        removeAnimationCallback();
        return false;
    } else if (mResumed) {
        mResumed = false;
        if (mPauseTime > 0) {
            mStartTime += (frameTime - mPauseTime);
        }
    }
	
	// mRunning 在 startAnimation()方法中就被置爲了 true
	// 但實際代碼狀況是 先添加回調,再調用 startAnimation方法
	// 因此有可能會出現 幀回調 快於 startAnimation方法 先運行,
	// 若是出現這種狀況,則此時的 mRunning狀態值爲false,就進入此分支進行處理
    if (!mRunning) {
        // 處理延時操做,若是未延時,此時的 mStartTime==frameTime,在首行代碼即是作這操做
        if (mStartTime > frameTime && mSeekFraction == -1) {
            return false;
        } else {
            // 若是還未運行,則先將 mRunning置爲true,而後啓動動畫,startAnimation的邏輯在前面已經闡述
            mRunning = true;
            startAnimation();
        }
    }

	// 第一次進來時,mLastFrameTime爲-1,則進入分支
	// mLastFrameTime用於記錄 最後一幀到達的時間(以毫秒爲單位)
    if (mLastFrameTime < 0) {
    	// 這裏是進行 mStartTime 的調整,由於 初始化開始時間 和 實際繪製幀 之間是有可能存在誤差
    	// 咱們這場景中 mSeekFraction 一直爲 -1,因此無需理會
        if (mSeekFraction >= 0) {
            long seekTime = (long) (getScaledDuration() * mSeekFraction);
            mStartTime = frameTime - seekTime;
            mSeekFraction = -1;
        }
        mStartTimeCommitted = false;
    }
    // 刷新最後一幀到達的時間
    mLastFrameTime = frameTime;

	// 這一句是爲了保證 當前幀時間 必須在開始的時間以後。
	// 保證不會逆向而行的出現,但這種狀況不多見。
    final long currentTime = Math.max(frameTime, mStartTime);
    // 這裏面 便進行了值的回調,咱們接下來具體分析
    boolean finished = animateBasedOnTime(currentTime);

	// 是否動畫已結束,結束的話進行生命週期的回調通知
    if (finished) {
        endAnimation();
    }
    return finished;
}
複製代碼

進入 animateBasedOnTime 方法,該方法會經過當前時間計算出當前動畫的進度,最後經過 animateValue 方法,進行更新回調,這樣就達到了 後續幀 的更新目的。

// ValueAnimator 類
boolean animateBasedOnTime(long currentTime) {
    boolean done = false;
    if (mRunning) {
        // 獲取縮放時長,但縮放因子爲 1,因此一直爲動畫時長
        final long scaledDuration = getScaledDuration();

        // 計算 fraction ,其實就是 已經運行時間佔 動畫時長的百分比
        final float fraction = scaledDuration > 0 ?
                (float) (currentTime - mStartTime) / scaledDuration : 1f;

        // 獲取 動畫的總體進度 (帶循環次數)
        final float lastFraction = mOverallFraction;

        // 是否爲新的迭代
        final boolean newIteration = (int) fraction > (int) lastFraction;

        // 最後一次迭代完成
       	// FLAG(22)
        final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
                (mRepeatCount != INFINITE);

        // 若是時長爲0,則直接結束
        if (scaledDuration == 0) {
            // 0時長的動畫,忽略重複計數 並 結束動畫
            done = true;
        } else if (newIteration && !lastIterationFinished) {    // 爲新的迭代 且 不是最後一次
        	// 回調 動畫循環次數 
            if (mListeners != null) {
                int numListeners = mListeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    mListeners.get(i).onAnimationRepeat(this);
                }
            }
        } else if (lastIterationFinished) { // 最後一次
            done = true;
        }

        // 更新 動畫的總體進度
        mOverallFraction = clampFraction(fraction);
        // 當前迭代的fraction(即不包含迭代次數),getCurrentIterationFraction方法在前面已經分析
        float currentIterationFraction = getCurrentIterationFraction(
                mOverallFraction, mReversing);
        // 進行值更新回調
        animateValue(currentIterationFraction);
    }
    return done;
}
複製代碼

最後就是 動畫終止 的問題,咱們前面也提到了根據 animateBasedOnTime的返回值來決定是否終止動畫,而在 animateBasedOnTime 方法中,返回true的地方,有兩個:

  1. 動畫時長爲0;
  2. 最後一次迭代完畢,至於判斷是否完成最後一次迭代,則經過判斷當前進度是否已經大於咱們循環的次數,而且動畫不是無限循環播放,判斷的代碼能夠看FLAG(22)。

若是 animateBasedOnTime 返回了true,便執行終止代碼,即執行 endAnimation 方法,具體代碼以下。能夠看到,該方法主要是執行標記位的復位回調的清楚生命週期監聽器回調。在FLAG(23)的代碼,則最終回調到咱們在 「第六行代碼」 時設置的 生命週期監聽器。

private void endAnimation() {
    // 若是已經終止了,就再也不重複執行
    if (mAnimationEndRequested) {
        return;
    }

    // 移除 回調
    removeAnimationCallback();

    // 將 動畫 置爲已經 終止
    mAnimationEndRequested = true;
    mPaused = false;

    boolean notify = (mStarted || mRunning) && mListeners != null;
    /** * 若是有 須要回調, 但還未進行運行,說明 須要先回調一次 * {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator)} */
    if (notify && !mRunning) {
        notifyStartListeners();
    }

    mRunning = false;
    mStarted = false;
    mStartListenersCalled = false;
    mLastFrameTime = -1;
    mFirstFrameTime = -1;
    mStartTime = -1;

    /** * 調用回調 {@link android.animation.Animator.AnimatorListener#onAnimationEnd(Animator)} */
    if (notify && mListeners != null) {
        ArrayList<AnimatorListener> tmpListeners =
                (ArrayList<AnimatorListener>) mListeners.clone();
        int numListeners = tmpListeners.size();
        for (int i = 0; i < numListeners; ++i) {
        	// FLAG(23)
            tmpListeners.get(i).onAnimationEnd(this, mReversing);
        }
    }

    mReversing = false;
    if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
        Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, getNameForTrace(),
                System.identityHashCode(this));
    }
}
複製代碼

最後咱們還須要折回去FLAG(24),說下咱們常常用來終止動畫的 cancel 方法。cancel 方法的具體代碼以下,咱們會發現若是已經開始動畫,但未運行(即mRunning 爲 false),則會先走一次 notifyStartListeners 方法,保證調用了 生命週期監聽器中的 onAnimationStart 方法,緊接着調用了 onAnimationCancel 方法,最後執行咱們上面提到的 endAnimation 方法進行終止動畫,而且回調 onAnimationEnd 方法。

@Override
public void cancel() {
    if (Looper.myLooper() == null) {
        throw new AndroidRuntimeException("Animators may only be run on Looper threads");
    }

    /** * 若是已經請求結束,則經過前一個end()或cancel()調用,執行空操做 * 直到動畫再次啓動。 */
    if (mAnimationEndRequested) {
        return;
    }

    /** * 當動畫已經開始 或 已經運行 而且須要回調 */
    if ((mStarted || mRunning) && mListeners != null) {
        /** * 若是還沒運行,則先進行回調 {@link android.animation.Animator.AnimatorListener#onAnimationStart(Animator)} */
        if (!mRunning) {
            // If it's not yet running, then start listeners weren't called. Call them now.
            notifyStartListeners();
        }

        ArrayList<AnimatorListener> tmpListeners =
                (ArrayList<AnimatorListener>) mListeners.clone();
        for (AnimatorListener listener : tmpListeners) {
            /** * 進行回調 {@link android.animation.Animator.AnimatorListener#onAnimationCancel(Animator)} */
            listener.onAnimationCancel(this);
        }
    }

    // 進行終止動畫
    endAnimation();

}
複製代碼

至此,屬性動畫的源碼分析便完成了。

4、實戰

一、多維雷達圖

文章開頭出現的就是如下效果圖,如今咱們來進行拆解實現。

效果圖

動畫分析

繪製相對應維度的雷達圖,在設置完數據後,進行設置屬性動畫,最後根據屬性動畫回調值進行每一個維度的展開。emmm,有些抽象。咱們進行拆解爲須要的零件:

  1. 每一個頂點的座標;
  2. 維度展開的屬性動畫;

準備零件

(1)頂點座標 一圖勝千言,咱們以六維雷達圖爲例,以比較有表明性的A,B,C三點來計算其座標。但這裏面有一個前提是,須要將畫布的原點移至view的中心。接下來具體的計算請看圖,中間涉及到一些簡單的三角函數,這裏就不過多的說明。

根據圖片中的計算規則,咱們能夠得知以 畫布的負y軸 爲基準,依次使用 sin(角度) * L 得出該點的 x座標,用 cos(角度) * L 得出該點的 y座標 。具體的代碼以下:

// 循環遍歷計算頂點座標
for (int i = 0; i < mDimenCount; ++i) {
    PointF point = new PointF();
    // 當前角度
    double curAngle = i * mAngle;
    // 轉弧度制
    double radian = Math.toRadians(curAngle);
    // 計算其 x、y 的座標
    // y軸須要進行取反,由於canvas的座標軸和咱們數學中的座標軸的y軸正好是上下相反的
    point.x = (float) (mLength * Math.sin(radian));
    point.y = (float) -(mLength * Math.cos(radian));
    mVertexList.add(point);
}
複製代碼

(2)維度展開的屬性動畫 從第一小節咱們獲得了全部頂點的座標,再根據傳入的數據(數據是以百分比傳入,即0f-1f),即可以計算出每一個維度的數據的最終頂點座標,具體代碼以下

/** * 計算數據的頂點座標 * * @param isBase 是否爲 基礎數據 */
private void calculateDataVertex(boolean isBase) {

    List<Data> calDataList = isBase ? mBaseDataList : mDataList;

    for (int i = 0; i < calDataList.size(); ++i) {

        Data data = calDataList.get(i);

        // 獲取 比例數據
        List<Float> pointDataList = data.getData();

        // 設置路徑
        Path curPath = new Path();
        data.setPath(curPath);

        curPath.reset();
        for (int j = 0; j < pointDataList.size(); ++j) {

            // 當前維度的數據比例
            float ratio = pointDataList.get(j);
            // 當前維度的頂點座標
            PointF curDimenPoint = mVertexList.get(j);

            if (j == 0) {
                curPath.moveTo(curDimenPoint.x * ratio,
                        curDimenPoint.y * ratio);
            } else {
                curPath.lineTo(curDimenPoint.x * ratio,
                        curDimenPoint.y * ratio);
            }

        }
        curPath.close();

    }

}
複製代碼

通過以上代碼的計算,獲得每一個數據中每一個維度的最終頂點最座標,最後就是設置的屬性動畫起始值和終止值,以及更新處理。

起始值固然是 0,而終止值是 數據量個數 * (維度數-1),動畫時長爲 每一個維度的動畫時長 * 終止值。具體以下代碼

mTotalLoopCount = (mDimenCount - 1) * mDataList.size();
mAnimator = ValueAnimator.ofFloat(0f, mTotalLoopCount);
mAnimator.setDuration(DURATION * mTotalLoopCount);
mAnimator.setInterpolator(new LinearInterpolator());
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {

        float value = (float) animation.getAnimatedValue();

        // 整數部分即爲當前的動畫數據下標
        mCurLoopCount = (int) value;

        // 小數部分極爲當前維度正在展開的進度百分比
        mAnimCurValue = value - mCurLoopCount;

        invalidate();
    }
});
mAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        // 動畫結束,將狀態置爲初始狀態,並再刷新一次,讓最後的數據所有顯示
        mCurState = INIT;
        invalidate();
    }
});
複製代碼

最後就是如何將這個值使用起來,由於咱們傳入的是浮點數,因此在 AnimatorUpdateListener 回調時,得到的數會有 整數部分小數部分 ,對 整數部分 進行 除以(維度數-1),獲得 當前的數據量下標;對 整數部分 進行 (維度數-1)取餘,再加1,獲得 當前數據的維度數,而小數部分就是咱們的維度進度。 代碼以下:

// 當前數據的下標(-1由於第一個維度不用動畫)
int curIndex = mCurLoopCount / (mDimenCount - 1);
// 當前數據的維度(-1由於第一個維度不用動畫)
int curDimen = (mCurLoopCount % (mDimenCount - 1)) + 1;
複製代碼

組裝零件

零件都已經備好了,組裝起來就是咱們看到的效果。由於代碼稍微較長,但主要點咱們已經攻破了,而且代碼註釋也比較多,這裏就再也不貼出來了,須要的請進傳送門

二、錶盤指示器

文章最開始出現的第二個就是如下這張效果圖,具體的操做其實和 「多維雷達圖」 沒有太多的出入,只是將維度的展開,變爲 畫布的旋轉後繪製指針,達到指針旋轉的效果,再加上插值器的公式輔助,到達擺動迴盪的效果。限於文章篇幅過長這裏就再也不具體闡述,有興趣的同窗請入傳送門

效果圖

5、寫在最後

屬性動畫是高級UI中又一把不可缺乏的利器,使用得當,能讓整個界面富有交互感,提升用戶體驗。最後若是你從這篇文章有所收穫,請給我個贊❤️,並關注我吧。文章中若有理解錯誤或是晦澀難懂的語句,請評論區留言,咱們進行討論共同進步。你的鼓勵是我前進的最大動力。

高級UI系列的Github地址:請進入傳送門,若是喜歡的話給我一個star吧😄

若是須要更多的交流與探討,能夠經過如下微信二維碼加小盆友好友。

相關文章
相關標籤/搜索