Android 屬性動畫詳解與源碼分析

一璃赡、前言

關(guān)于什么是動畫判哥,動畫的相關(guān)概念等等這里就不講了。這里僅表述一下個人觀點碉考,個人認知是:
1.動畫增加了 UI 展示的動態(tài)性塌计,使得UI看起來更具生機。同時侯谁,一些酷炫的動畫一定程度上也會提高應(yīng)用的 Bigger锌仅。但這里要把握一個度,一個頁面中不宜有過多的動畫墙贱,1 到 2 個明顯的即可热芹,尤其動畫不能掩蓋業(yè)務(wù)的主旨。
2.動畫更好的表達了用戶操作的反饋惨撇,同時也能更好的給用戶以指導(dǎo)操作伊脓。

下面的思維導(dǎo)圖展示了 Android 原生所支持的主要動畫分類,概念以及相關(guān)場景魁衙。


Android動畫分類.png

這里概括了 Android 中 8 種主要的動畫报腔,就目前而言,其中最常用的便是屬性動畫(視圖動畫現(xiàn)在應(yīng)該用的少了)纺棺,轉(zhuǎn)場動畫以及視圖狀態(tài)動畫,其余動畫基本上我們是很少接觸的邪狞,主要是國內(nèi)的應(yīng)用開發(fā)中不實用祷蝌。

當(dāng)然,除了 Android 原生動畫之外帆卓,目前第 3 方的 airbnb/lottie-android 動畫方案巨朦,更是給我們提供了跨平臺的動畫解決方案。

在這篇文章里剑令,我們先主要來看看屬性動畫糊啡。

二、屬性動畫概覽

1.基本概念

屬性動畫系統(tǒng)是一個強大的框架吁津,允許您使用幾乎任何對象來作動畫棚蓄,甚至不用管它是否是繪制到屏幕上的一個View堕扶。其主要的原理就是通過定義動畫以隨時間變化而更改對象的某一屬性。更簡單地說梭依,就是修改對象的某個屬性值來實現(xiàn)動畫的稍算。

2.主要特征

持續(xù)時間:您可以指定動畫的持續(xù)時間。默認長度為300毫秒役拴。
時間插值:您可以指定如何計算屬性值作為動畫當(dāng)前已用時間的函數(shù)糊探。
重復(fù)計數(shù)和行為:您可以指定是否在到達持續(xù)時間結(jié)束時重復(fù)動畫以及重復(fù)動畫的次數(shù)。您還可以指定是否要反向播放動畫河闰。將其設(shè)置為反向向前播放動畫然后反復(fù)播放動畫科平,直到達到重復(fù)次數(shù)。
動畫設(shè)置:您可以將動畫分組為一起或按順序或在指定延遲后播放的邏輯集姜性。
幀刷新延遲:您可以指定刷新動畫幀的頻率瞪慧。默認設(shè)置為每10毫秒刷新一次,但應(yīng)用程序刷新幀的速度最終取決于系統(tǒng)整體的繁忙程度以及系統(tǒng)為基礎(chǔ)計時器提供服務(wù)的速度污抬。

這是翻譯自官網(wǎng)對屬性動畫的特征概括汞贸,但稍有經(jīng)驗的你不難發(fā)現(xiàn),其他類型的動畫都會有此概念印机。

3.框架類圖

Main.jpg

如上面類圖所見矢腻,屬性動畫主要被分成 3 個部分,用于組織動畫的 Animator射赛,用于計算時間插值的 插值器 以及用于計算結(jié)果的 估值器多柑。

4.插值器Interpolator

插值器的作用是使得屬性值根據(jù)時間的變化從初始值過渡到結(jié)束值的變化規(guī)律。其實質(zhì)是一個數(shù)學(xué)函數(shù) y = f(x)楣责,定義域 x 屬于 (0.0竣灌,1.0) 的 float 值,值域 y 也是 (0.0秆麸,1.0) 的 float 值初嘹,曲線的斜率是速度。如上圖框架類中沮趣,Android 為我們定義了 10 個內(nèi)置的插值器屯烦,其基本上滿足了我們大部分的需求。當(dāng)然房铭,我們也可以自定義來實現(xiàn)自己的插值器驻龟。

下面通過 2 個簡單的 gif 動畫來粗略的感受一下插值器。

簡單右移動畫.gif

綜合右移缸匪,旋轉(zhuǎn)翁狐,縮放,alpha

5.估值器

估值器也是一個函數(shù)凌蔬,插值器是得到時間的變化規(guī)律露懒,而估值器則是根據(jù)時間的變化規(guī)律計算得到每一步的運算結(jié)果闯冷。在動畫的監(jiān)聽中根據(jù)計算的結(jié)果來改變目標(biāo)的屬性值。利用估值器我們可以實現(xiàn)一些曲線運動如拋物線隐锭,貝塞爾曲線等動畫窃躲。下圖是通過自定義估值器實現(xiàn)的一個簡單的曲線運動。


曲線.gif

三钦睡、源碼分析

不管是 Android 源碼還是其他第三方的源碼框架蒂窒,其代碼構(gòu)成往往都是比較繁多且復(fù)雜的。我分析代碼的一個簡單的方法論就是先寫一個最簡單的 demo荞怒,然后根據(jù) demo 沿著調(diào)用鏈來分析其主體流程洒琢,在分析的過程中再慢慢補齊相關(guān)的概念,模塊甚至是重要的細節(jié)部分褐桌。

1.demo

以 ObjectAnimator 為例來寫一個簡單的右移動畫衰抑。

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageViewMove,"translationX",0,100)
                        .setDuration(1 * 1000);
                objectAnimator.setInterpolator(new LinearInterpolator());
                objectAnimator.start();

2.代碼分析

先來看看時序圖,我們將按照時序的圖的順序一步一步來分析荧嵌。


ObjectAnimator.jpg

創(chuàng)建動畫

創(chuàng)建動畫就是創(chuàng)建動畫運行的基礎(chǔ)或者說是條件呛踊,前面 1 - 9 步都可以說是在創(chuàng)建動畫的基礎(chǔ)。從 ObjectAnimator.ofFloat()開始啦撮。

    /**
     * 構(gòu)建一個返回值為 float 的 ObjectAnimator 的實例
     *
     * @param target 作用于動畫的對象谭网。
     * @param propertyName 屬性名稱,要求對象須有setXXX() 方法赃春,且是 public 的愉择。
     * @param values,屬性變化的值织中,可以設(shè)置  1 個或者 多個锥涕。當(dāng)只有 1 個時,起始值為屬性值本身狭吼。當(dāng)有 2 個值時层坠,第 1 個為起始值,第 2 個為終止值刁笙。當(dāng)超過 2 個時破花,首尾值的定義與 2 個時一樣,中間值做需要經(jīng)過的值采盒。
     */
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

該方法的參數(shù)以作用都已經(jīng)放在注釋里面了旧乞。而這個方法里面所做的事情是蔚润,首先創(chuàng)建一個 ObjectAnimator 的實例磅氨,然后為該實例設(shè)置 values。那么嫡纠,繼續(xù)看 ObjectAnimator 的構(gòu)建烦租。
構(gòu)造 ObjectaAnimator

private ObjectAnimator(Object target, String propertyName) {
        setTarget(target);
        setPropertyName(propertyName);
    }

分別調(diào)用了 setTarget() 方法和setPropertyName()
setTarget()

public void setTarget(@Nullable Object target) {
        final Object oldTarget = getTarget();
        if (oldTarget != target) {
            if (isStarted()) {
                cancel();
            }
            mTarget = target == null ? null : new WeakReference<Object>(target);
            // New target should cause re-initialization prior to starting
            mInitialized = false;
        }
    }

存在舊動畫對象(也可為 null) 與新設(shè)置的動畫對象不一致延赌,如果舊動畫是開始了的狀態(tài),則先取消動畫叉橱,然后將動畫對象以弱引用對象為記錄下來挫以。
setPropertyName()

public void setPropertyName(@NonNull String propertyName) {
        // mValues could be null if this is being constructed piecemeal. Just record the
        // propertyName to be used later when setValues() is called if so.
        if (mValues != null) {
            PropertyValuesHolder valuesHolder = mValues[0];
            String oldName = valuesHolder.getPropertyName();
            valuesHolder.setPropertyName(propertyName);
            mValuesMap.remove(oldName);
            mValuesMap.put(propertyName, valuesHolder);
        }
        mPropertyName = propertyName;
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

主要就是記錄下 propertyName 的名字。而如果已經(jīng)有這個 propertyName窃祝,則會替換其相應(yīng)的 PropertyValuesHolder掐松,這里用了一個 HashMap 來保存 propertyName 和 PropertyValuesHolder。關(guān)于 PropertyValuesHolder 先來看看其類圖結(jié)構(gòu)粪小。對于屬性動畫來說大磺,其屬性相關(guān)的變量都被封裝在了 PropertyValuesHolder 里。

PropertyValuesHolder.jpg

這里我們還要記住的是 propertyName 是 "translationX"探膊。接下來看 setFloatValues() 方法杠愧。
setFloatValues()

 @Override
    public void setFloatValues(float... values) {
        if (mValues == null || mValues.length == 0) {
            // 當(dāng)前還沒有任何值
            if (mProperty != null) {
                setValues(PropertyValuesHolder.ofFloat(mProperty, values));
            } else {
                setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
            }
        } else {
            // 當(dāng)前已經(jīng)有值的情況,調(diào)用父類的 setFloatValues()
            super.setFloatValues(values);
        }
    }

父類逞壁,即 ValueAnimator 流济,其方法setFloatValues() 如下。
ValueAnimator#setFloatValues()

public void setFloatValues(float... values) {
        if (values == null || values.length == 0) {
            return;
        }
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofFloat("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setFloatValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }

這里可以看出腌闯,不管是否調(diào)用父類的 setFloatValues()绳瘟。最后都是要將 values 逐個構(gòu)造成 PropertyValuesHolder,最后存放在前面所說的 HashMap 里面绑嘹。當(dāng)然稽荧,如果這里的 hashMap 還沒有初始化,則先會將其初始化工腋。所以這里面最關(guān)鍵的是要構(gòu)建出 PropertyValuesHolder 這個對象姨丈。那么就繼續(xù)來看看 PropertyValuesHolder#ofFloat() 方法。
PropertyValuesHolder#ofFloat()

    public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
        return new FloatPropertyValuesHolder(propertyName, values);
    }

構(gòu)造 FloatPropertyValuesHolder擅腰,當(dāng)然蟋恬,這里相應(yīng)的還有 IntPropertyValuesHolder、MultiIntValuesHolder以及MultiFloatValuesHolder趁冈,都是 PropertyValuesHolder 的子類歼争。這里就只關(guān)注 FloatPropertyValuesHolder 吧。
FloatPropertyValuesHolder

        public FloatPropertyValuesHolder(String propertyName, float... values) {
            super(propertyName);
            setFloatValues(values);
        }

FloatPropertyValuesHolder 構(gòu)造函數(shù)比較簡單渗勘,調(diào)用父類的構(gòu)造方法并傳遞了 propertyName沐绒,關(guān)鍵是進一步 setFloatValues() 方法的調(diào)用,其又進一步調(diào)用了父類的 setFloatValues()旺坠,在父類的 setFloatValues() 方法里初始化了動畫的關(guān)鍵幀乔遮。
PropertyValuesHolder#setFloatValues()

    public void setFloatValues(float... values) {
        mValueType = float.class;
        mKeyframes = KeyframeSet.ofFloat(values);
    }

進一步調(diào)用了 KeyframeSet#ofFloat() 方法以完成關(guān)鍵幀的構(gòu)造。KeyframeSet 是接口 Keyframe 的實現(xiàn)類取刃。
KeyframeSet#ofFloat()

public static KeyframeSet ofFloat(float... values) {
        boolean badValue = false;
        int numKeyframes = values.length;
        // 至少要 2 幀
        FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
        // 然后構(gòu)造出每一幀蹋肮,每一幀中主要有 2 個重要的參數(shù) fraction 以及 value
        if (numKeyframes == 1) {
            keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
            keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
            if (Float.isNaN(values[0])) {
                badValue = true;
            }
        } else {
            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]);
                if (Float.isNaN(values[i])) {
                    badValue = true;
                }
            }
        }
        if (badValue) {
            Log.w("Animator", "Bad value (NaN) in float animator");
        }
        // 最后將所有的 關(guān)鍵幀 匯集到一個集合中
        return new FloatKeyframeSet(keyframes);
    }

這段代碼看起來多出刷,但其實結(jié)構(gòu)很簡單,其主要內(nèi)容是:
(1) 構(gòu)造動畫的關(guān)鍵幀坯辩,且動畫里至少要有 2 個關(guān)鍵幀馁龟。
(2) 關(guān)鍵幀中有 2 個重要的參數(shù),fraction這個可以看成是關(guān)鍵幀的序號漆魔,value 關(guān)鍵幀的值坷檩,可能是起始值,也可能是中間的某個值改抡。
(3) 最后將關(guān)鍵幀匯集成一個關(guān)鍵幀集返回給 PropertyValuesHolder淌喻。

到這里就完成創(chuàng)建動畫的 1 ~ 6 步,其主要是 2 件事情雀摘,屬性封裝與關(guān)鍵幀構(gòu)建裸删。接下來繼續(xù)看 setDuration() 與 setInterpolator() 方法。

setDuration()

    @Override
    @NonNull
    public ObjectAnimator setDuration(long duration) {
        super.setDuration(duration);
        return this;
    }

調(diào)用了父類 ValueAnimator 的 setDuration()阵赠。
ValueAnimator#setDuration()

    @Override
    public ValueAnimator setDuration(long duration) {
        if (duration < 0) {
            throw new IllegalArgumentException("Animators cannot have negative duration: " +
                    duration);
        }
        mDuration = duration;
        return this;
    }

setDuration() 只是簡單的存儲下 duration 的值涯塔,僅此而已,那么繼續(xù)分析 setInterpolator()清蚀。
setInterpolator()

@Override
    public void setInterpolator(TimeInterpolator value) {
        if (value != null) {
            mInterpolator = value;
        } else {
            mInterpolator = new LinearInterpolator();
        }
    }

setInterpolator() 方法也很簡單匕荸,只是簡單的存儲,并且如果傳遞的是 null 的話枷邪,則默認使用的便是 LinearInterpolator榛搔,即線性插值器。我們這里的假設(shè)的場景也是設(shè)置了 LinearInterpolator东揣,這是最簡單的插值器践惑,其作用就是完成勻速運動。這里借助 LinearInterpolator 來分析一下插值器嘶卧。

關(guān)于插值器的概念已經(jīng)在第二節(jié)第 4 小節(jié)里面有介紹了尔觉。且在第 3 小節(jié)中的框架類圖中也完整的描述了插值器的繼承關(guān)系。其最關(guān)鍵的定義就在 TimeInterpolator 這個接口中芥吟,來看看這個接口侦铜。

/**
 * 插值器定義了動畫變化的頻率,其可以是線性的也可以是非線性的钟鸵,如加速運動或者減速運動归敬。
 */
public interface TimeInterpolator {

    /**
     * 這里傳進來的 input 代表當(dāng)前時間與總時間的比芹彬,根據(jù)這個時間占比返回當(dāng)前的變化頻率旷祸。其輸出與輸值都在 [0,1] 之間熊榛。
     */
    float getInterpolation(float input);
}

插值器的關(guān)鍵定義便是實現(xiàn) getInterpolation() 方法,即根據(jù)當(dāng)前動畫運行的時間占比來計算當(dāng)前動畫的變化頻率。那么來看看 LinearInterpolator 的 getInterpolation() 實現(xiàn)羞秤。
LinearInterpolator#getInterpolation()

public float getInterpolation(float input) {
        return input;
    }

對,就是返回原值左敌,因為時間的變化肯定始終都是勻速的瘾蛋。到這里,創(chuàng)建動畫的 1 ~ 9 步都已經(jīng)完成了矫限〔负撸可時間究竟是怎么樣變化的,getInterpolation() 又是怎樣被調(diào)用的叼风?這就是接下來要分析的啟動動畫取董。

啟動動畫

啟動動畫從 start() 方法開始。

@Override
    public void start() {
        AnimationHandler.getInstance().autoCancelBasedOn(this);
        if (DBG) {
            Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
            for (int i = 0; i < mValues.length; ++i) {
                PropertyValuesHolder pvh = mValues[i];
                Log.d(LOG_TAG, "   Values[" + i + "]: " +
                    pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
                    pvh.mKeyframes.getValue(1));
            }
        }
        super.start();
    }

先確認動畫已經(jīng)取消无宿。這個方法里的重要的那句代碼就是調(diào)用父類 ValueAnimator 的 start()茵汰。父類對外的 start() 方法很簡單,其主要的實現(xiàn)在另一個重載的私有 start() 方法上孽鸡,來繼續(xù)分析蹂午。

// 參數(shù) playBackwards 代表動畫是否是逆向的
private void start(boolean playBackwards) {
       .....
        mReversing = playBackwards;
        // 重置脈沖為 "true"
        mSelfPulse = !mSuppressSelfPulseRequested;
        .....
        // 添加脈沖回調(diào)用
        addAnimationCallback(0);

        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            // If there's no start delay, init the animation and notify start listeners right away
            // to be consistent with the previous behavior. Otherwise, postpone this until the first
            // frame after the start delay.
            startAnimation();
            if (mSeekFraction == -1) {
                // No seek, start at play time 0. Note that the reason we are not using fraction 0
                // is because for animations with 0 duration, we want to be consistent with pre-N
                // behavior: skip to the final value immediately.
                setCurrentPlayTime(0);
            } else {
                setCurrentFraction(mSeekFraction);
            }
        }
    }

這個方法原本很多,但我們只需要關(guān)注關(guān)鍵的調(diào)用彬碱,其中之一是 addAnimationCallback()豆胸,其主要是向 AnimationHander 添加一個回調(diào)接口AnimationHandler.AnimationFrameCallback。如下代碼巷疼。
addAnimationFrameCallback

/**
     * Register to get a callback on the next frame after the delay.
     */
    public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
        if (mAnimationCallbacks.size() == 0) {
            getProvider().postFrameCallback(mFrameCallback);
        }
        if (!mAnimationCallbacks.contains(callback)) {
            mAnimationCallbacks.add(callback);
        }

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

ValueAnimator 就實現(xiàn)了 AnimationFrameCallback晚胡,所以這里添加的是 ValueAnimator 的實例。且最終被添加到 mAnimationCallbacks 這個隊列中嚼沿。這個是很重要的估盘,后面還會再重點關(guān)注的。而接下來是另一個調(diào)用 startAnimation()
startAnimation()

private void startAnimation() {
        ......
        mAnimationEndRequested = false;
        initAnimation();
        mRunning = true;
        if (mSeekFraction >= 0) {
            mOverallFraction = mSeekFraction;
        } else {
            mOverallFraction = 0f;
        }
        if (mListeners != null) {
            // 通過動畫監(jiān)聽器動畫開始了
            notifyStartListeners();
        }
    }

關(guān)鍵調(diào)用 initAnimation()

void initAnimation() {
        if (!mInitialized) {
            int numValues = mValues.length;
            for (int i = 0; i < numValues; ++i) {
                mValues[i].init();
            }
            mInitialized = true;
        }
    }

mValues 是 PropertyValuesHolder 數(shù)組骡尽,這里的目的是初始化 PropertyValuesHolder忿檩。

void init() {
        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);
        }
    }

init() 方法的主要目的是就是給關(guān)鍵幀設(shè)置估值器。因為我們前面調(diào)用的是 ObjectAnimator#ofFloat() 方法爆阶,所以這里默認給的就是 FloatEvaluator燥透。這里也來分析一下估值器。估值器的相關(guān)概念已經(jīng)在第二節(jié)第 5 小節(jié)中有所描述辨图,并且根據(jù)框架類圖班套,其主要是定義了 TypeEvaluator 接口。
TypeEvaluator

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

fraction 代表了startValue 到 endValue 之間的比例故河,startValue 與 endValue 是我們自己在 ofFloat() 調(diào)用時設(shè)定的那個吱韭。返回結(jié)果就是一個線性的結(jié)果,在 startValue 與 endValue 之間。那么來看看 FloatEvaluator 的實現(xiàn)理盆。
FloatEvaluator

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);
    }
}

很簡單痘煤,就是起點值加上當(dāng)前的段值。到這里猿规,我們應(yīng)該對估值器有一個更深的認知了衷快。那么,再回到最初的 start() 方法里姨俩,經(jīng) startAnimation() 設(shè)置了 KeyFrame 的估值器后蘸拔,接下來就會進一步調(diào)用 setCurrentPlayTime() 來開始動畫。
setCurrentPlayTime()

    public void setCurrentPlayTime(long playTime) {
        float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
        setCurrentFraction(fraction);
    }

初始時調(diào)用的是setCurrentPlayTime(0)环葵,也就是 playTime 為 0调窍,而 mDuration 就是我們自己通過 setDuration() 來設(shè)置的。所以這里得到的 fraction 也是 0张遭。進一步看 setCurrentFraction() 方法邓萨。

public void setCurrentFraction(float fraction) {
        // 再次調(diào)用 initAnimation() ,前面初始化過了菊卷,所以這里是無用的
        initAnimation();
        // 校準(zhǔn) fraction 為 [0, mRepeatCount + 1]
        fraction = clampFraction(fraction);
        mStartTimeCommitted = true; // do not allow start time to be compensated for jank
        if (isPulsingInternal()) {
            // 隨機時間先誉?
            long seekTime = (long) (getScaledDuration() * fraction);
            // 獲取動畫的當(dāng)前運行時間
            long currentTime = AnimationUtils.currentAnimationTimeMillis();
            // Only modify the start time when the animation is running. Seek fraction will ensure
            // non-running animations skip to the correct start time.
            // 得到開始時間
            mStartTime = currentTime - seekTime;
        } else {
            // If the animation loop hasn't started, or during start delay, the startTime will be
            // adjusted once the delay has passed based on seek fraction.
            mSeekFraction = fraction;
        }
        mOverallFraction = fraction;
        final float currentIterationFraction = getCurrentIterationFraction(fraction, mReversing);
        // 執(zhí)行動畫,注意這里會先調(diào)用子類的 animateValue() 方法
        animateValue(currentIterationFraction);
    }

前面都是一些時間的計算的烁,得到當(dāng)前真正的currentIterationFraction褐耳,最后會通過調(diào)用animateValue() 來執(zhí)行動畫。而這里需要同時關(guān)注父類與子類的 animateValue() 方法渴庆。
子類 ObjectAnimator#animateValue()

void animateValue(float fraction) {
        final Object target = getTarget();
        if (mTarget != null && target == null) {
            // We lost the target reference, cancel and clean up. Note: we allow null target if the
            /// target has never been set.
            cancel();
            return;
        }
        // 調(diào)用父類的 animateValue() 铃芦,這個很關(guān)鍵,時間插值與估值器的計算都在父類的 animateValue() 方法中進行的襟雷。
        super.animateValue(fraction);
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            // 這里的 mValues 的是PropertyValuesHolder[]刃滓,也就是在 PropertyValuesHolder 里面來改變了目標(biāo) target 的屬性值。
            mValues[i].setAnimatedValue(target);
        }
    }

父類 ValueAnimator#animateValue()

void animateValue(float fraction) {
        // 獲取時間插值
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        // 將時間插值送給估值器耸弄,計算出 values
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
       // 發(fā)出通知
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

animateValue() 的主要功能都已經(jīng)在注釋中說明了咧虎,其主要就是 2 步,一步計算時間插值和估值器计呈,另一步是調(diào)用 PropertyValuesHolder 來改變屬性砰诵。

void setAnimatedValue(Object target) {
        if (mProperty != null) {
            mProperty.set(target, getAnimatedValue());
        }
        if (mSetter != null) {
            try {
                mTmpValueArray[0] = getAnimatedValue();
                // 通過反射調(diào)用來修改屬性值
                mSetter.invoke(target, mTmpValueArray);
            } catch (InvocationTargetException e) {
                Log.e("PropertyValuesHolder", e.toString());
            } catch (IllegalAccessException e) {
                Log.e("PropertyValuesHolder", e.toString());
            }
        }
    }

這里就是通過屬性的 Setter 方法來修改屬性的。

分析到這里捌显,就完成了動畫的一幀關(guān)鍵幀的執(zhí)行茁彭。那么你一定會感到奇怪了,剩下的幀是怎么驅(qū)動的呢扶歪?還是得回到 start() 方法里面理肺,在這里最初分析到 addAnimationFrameCallback() 方法。這個方法里等于是向AnimationHandler注冊了AnimationHandler.AnimationFrameCallback。這個 callback 中其中之一的方法是 doAnimationFrame()妹萨。在 ValueAnimator 的實現(xiàn)中如下年枕。

public final boolean doAnimationFrame(long frameTime) {
        .....
        boolean finished = animateBasedOnTime(currentTime);
        if (finished) {
            endAnimation();
        }
        return finished;
    }

這段代碼原來也是很長的,我們只看關(guān)鍵調(diào)用 animateBasedOnTime()

boolean animateBasedOnTime(long currentTime) {
        boolean done = false;
        if (mRunning) {
            .....
            float currentIterationFraction = getCurrentIterationFraction(
                    mOverallFraction, mReversing);
            animateValue(currentIterationFraction);
        }
        return done;
    }

前面的計算過程乎完,這里就省略了熏兄,其主要的目的也還是計算出 currentIterationFraction。然后再通過 animateValue() 方法來執(zhí)行動畫囱怕。可以看到只要 doAnimationFrame() 被不斷的調(diào)用毫别,就會產(chǎn)生動畫的一個關(guān)鍵幀娃弓。如果關(guān)鍵幀是連續(xù)的,那么最后也就產(chǎn)生了我們所看到的動畫岛宦。

再來分析doAnimationFrame() 是如何被不斷調(diào)用的台丛。這個需要回到 AnimationHandler 中來,在 AnimationHandler 中有一個非常重要的 callback 實現(xiàn)——Choreographer.FrameCallback砾肺。

private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
        @Override
        public void doFrame(long frameTimeNanos) {
            doAnimationFrame(getProvider().getFrameTime());
            if (mAnimationCallbacks.size() > 0) {
                getProvider().postFrameCallback(this);
            }
        }
    };

了解 VSync 的同學(xué)都知道挽霉,Andorid 中的重繪就是由Choreographer在 1 秒內(nèi)產(chǎn)生 60 個 vsync 來通知 view tree 進行 view 的重繪的。而 vsync 產(chǎn)生后會調(diào)用它的監(jiān)聽者回調(diào)接口 Choreographer.FrameCallback变汪,也就是說侠坎,只要向Choreographer注冊了這個接口,就會每 1 秒里收到 60 次回調(diào)裙盾。因此实胸,在這里就實現(xiàn)了不斷地調(diào)用 doAnimationFrame() 來驅(qū)動動畫了。想必看到這里番官,你應(yīng)該明白了同學(xué)們常說的動畫掉幀的原因了吧庐完。如果 view 的繪制過于復(fù)雜,即在 15 ms 內(nèi)無法完成徘熔,那么就會使得中間某些幀跳過從而造成掉幀门躯。

到這里,屬性動畫的整個過程以及原理都分析完了酷师。下面來總結(jié)一下這個過程:

(1) 動畫是由許多的關(guān)鍵幀組成的讶凉,這是一個動畫能夠動起來的最基本的原理。
(2) 屬性動畫的主要組成是 PropertyValuesHolder山孔,而 PropertyValuesHolder 又封裝了關(guān)鍵幀缀遍。
(3) 動畫開始后,其監(jiān)聽了 Choreographer 的 vsync饱须,使得其可以不斷地調(diào)用 doAnimationFrame() 來驅(qū)動動畫執(zhí)行每一個關(guān)鍵幀域醇。
(4) 每一次的 doAnimationFrame() 調(diào)用都會去計算時間插值,而通過時間插值器計算得到 fraction 又會傳給估值器,使得估值器可以計算出屬性的當(dāng)前值譬挚。
(5) 最后再通過 PropertyValuesHolder 所記錄下的 Setter 方法锅铅,以反射的方式來修改目標(biāo)屬性的值。當(dāng)屬性值一幀一幀的改變后减宣,形成連續(xù)后盐须,便是我們所見到的動畫。

四漆腌、后記

Android 原生動畫中贼邓,除了屬性動畫還有其他幾種動畫,在對屬性動畫有了一定的認知后闷尿,再來分析其他動畫的實現(xiàn)原理塑径,相信不會太難。后面有時間或者有需要會再來分析填具。

最后统舀,感謝你能讀到并讀完此文章,如果分析的過程中存在錯誤或者疑問都歡迎留言討論劳景。如果我的分享能夠幫助到你誉简,還請記得幫忙點個贊吧,謝謝盟广。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末闷串,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子筋量,更是在濱河造成了極大的恐慌窿克,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毛甲,死亡現(xiàn)場離奇詭異年叮,居然都是意外死亡,警方通過查閱死者的電腦和手機玻募,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門只损,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人七咧,你說我怎么就攤上這事跃惫。” “怎么了艾栋?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵爆存,是天一觀的道長。 經(jīng)常有香客問我蝗砾,道長先较,這世上最難降的妖魔是什么携冤? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮闲勺,結(jié)果婚禮上曾棕,老公的妹妹穿的比我還像新娘。我一直安慰自己菜循,他們只是感情好翘地,可當(dāng)我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著癌幕,像睡著了一般衙耕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勺远,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天橙喘,我揣著相機與錄音,去河邊找鬼谚中。 笑死渴杆,一個胖子當(dāng)著我的面吹牛寥枝,可吹牛的內(nèi)容都是我干的宪塔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼囊拜,長吁一口氣:“原來是場噩夢啊……” “哼某筐!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起冠跷,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤南誊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蜜托,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抄囚,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年橄务,在試婚紗的時候發(fā)現(xiàn)自己被綠了幔托。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜂挪,死狀恐怖重挑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棠涮,我是刑警寧澤谬哀,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站严肪,受9級特大地震影響史煎,放射性物質(zhì)發(fā)生泄漏谦屑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一劲室、第九天 我趴在偏房一處隱蔽的房頂上張望伦仍。 院中可真熱鬧,春花似錦很洋、人聲如沸充蓝。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽谓苟。三九已至,卻和暖如春协怒,著一層夾襖步出監(jiān)牢的瞬間涝焙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工孕暇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仑撞,地道東北人。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓妖滔,卻偏偏與公主長得像隧哮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子座舍,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容