android動畫如何更新UI(ValueAnimator源碼解析)

概述

android動畫經常會碰到卡頓,或者阻塞主進程之類的問題。
爲了排查此類問題,不得不對動畫原理了解一二,於是作此文。
此文圍繞兩個主線問題展開:

  1. ui更新的頻率是如何控制的?
    比如,1秒內會更新多少次?
  2. 每次更新UI的時候所帶的update的value是如何控制的?
    比如,現在有個0到100的動畫,在執行到30%的時候,value是多少?(可能非線性變化)

對動畫還是不太瞭解的讀者可以看下筆者之前的文章:
Android動畫總結 (valueAnimator、objectAnimator、AnimatorSet、PropertyValuesHolder、Interpolator)

ValueAnimator源碼

動畫平時使用的API較多,但是最終的原理都是一樣的,本文就選擇從ValueAnimator的源碼入手分析這兩個問題。

ui更新的頻率

Demo代碼如下:

    ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1000);
    valueAnimator.setDuration(2000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override public void onAnimationUpdate(ValueAnimator animation) {
        Log.d("test", animation.getAnimatedValue() + "");
      }
    });
    valueAnimator.start();

直接在onAnimationUpdate方法中打斷點,找到調用任務棧:
在這裏插入圖片描述
最終是進入Choreographer的doFrame方法。

Choreographer的doFrame方法是在繪製每一幀的時候調用的,因此動畫的更新頻率與幀的更新頻率一致。至於幀的更新頻率是多少就看具體手機了。(每秒執行doFrame的次數其實就是fps)

update的value如何控制?

首先看下getAnimatedValue源碼:

    PropertyValuesHolder[] mValues;
    
    public Object getAnimatedValue() {
        if (mValues != null && mValues.length > 0) {
            return mValues[0].getAnimatedValue();
        }
        return null;
    }

由此我們可以知道,取的邏輯沒有什麼特殊的地方,其值在更新的時候早就設置好了,因此我們要找下設置的邏輯。
我們再使用ValueAnimation的時候,真正觸發動畫的邏輯其實是start()方法,因此我們從start()方法源碼入手。經過如下調用,可以進入到設置value的地方:

  • ValueAnimation.start()
  • ValueAnimation.start(boolean playBackwards)
  • ValueAnimation.setCurrentFraction(float fraction)
  • ValueAnimation.animateValue(float fraction)
    PropertyValuesHolder[] mValues;
    
    void animateValue(float fraction) {
        fraction = mInterpolator.getInterpolation(fraction);
        mCurrentFraction = fraction;
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
            mValues[i].calculateValue(fraction);
        }
        if (mUpdateListeners != null) {
            int numListeners = mUpdateListeners.size();
            for (int i = 0; i < numListeners; ++i) {
                mUpdateListeners.get(i).onAnimationUpdate(this);
            }
        }
    }

fraction可以認爲是當前的值的百分比,比如我當前的值的範圍是0~100,當前是120,那麼fraction就是1.2f,如果當前是50,那麼fraction就是0.5f。

此處可以看到直接是將fraction傳入到PropertyValuesHolder.calculateValue()方法來計算的value。

    Keyframes mKeyframes = null;
    void calculateValue(float fraction) {
        Object value = mKeyframes.getValue(fraction);
        mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
    }

此處要了解Keyframes,可以從ValueAnimator.ofInt(int… values)從上至下看,也可以直接看PropertyValuesHolder中Keyframes的賦值來推導。
此處我們採用大家更熟悉的方式來,從ValueAnimator.ofInt(int… values)從上至下看:

    public static ValueAnimator ofInt(int... values) {
        ValueAnimator anim = new ValueAnimator();
        anim.setIntValues(values);
        return anim;
    }
    public void setIntValues(int... values) {
        if (values == null || values.length == 0) {
            return;
        }
        if (mValues == null || mValues.length == 0) {
            setValues(PropertyValuesHolder.ofInt("", values));
        } else {
            PropertyValuesHolder valuesHolder = mValues[0];
            valuesHolder.setIntValues(values);
        }
        // New property/values/target should cause re-initialization prior to starting
        mInitialized = false;
    }
    public void setIntValues(int... values) {
        mValueType = int.class;
        mKeyframes = KeyframeSet.ofInt(values);
    }
    public static KeyframeSet ofInt(int... values) {
        int numKeyframes = values.length;
        IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
        if (numKeyframes == 1) {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
            keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
        } else {
            keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
            for (int i = 1; i < numKeyframes; ++i) {
                keyframes[i] =
                        (IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
            }
        }
        return new IntKeyframeSet(keyframes);
    }

知道ValueAnimator.ofInt(int… values)其實就對應IntKeyframeSet之後,我們就可以以IntKeyframeSet爲例來看下getValue的代碼:

    @Override
    public Object getValue(float fraction) {
        return getIntValue(fraction);
    }
    public int getIntValue(float fraction) {
        if (fraction <= 0f) {
            final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
            final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(1);
            int prevValue = prevKeyframe.getIntValue();
            int nextValue = nextKeyframe.getIntValue();
            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 + (int)(intervalFraction * (nextValue - prevValue)) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                            intValue();
        } else if (fraction >= 1f) {
            final IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 2);
            final IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(mNumKeyframes - 1);
            int prevValue = prevKeyframe.getIntValue();
            int nextValue = nextKeyframe.getIntValue();
            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 + (int)(intervalFraction * (nextValue - prevValue)) :
                    ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).intValue();
        }
        IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
        for (int i = 1; i < mNumKeyframes; ++i) {
            IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                int prevValue = prevKeyframe.getIntValue();
                int nextValue = nextKeyframe.getIntValue();
                // Apply interpolator on the proportional duration.
                if (interpolator != null) {
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                return mEvaluator == null ?
                        prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                        ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                                intValue();
            }
            prevKeyframe = nextKeyframe;
        }
        // shouldn't get here
        return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).intValue();
    }

一般情況下,如果是線性插值器的話,fraction其實就只代表進度,那麼fraction是肯定>0且<1的,我們要看的代碼其實只有這一段:

        IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);
        for (int i = 1; i < mNumKeyframes; ++i) {
            IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
            if (fraction < nextKeyframe.getFraction()) {
                final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
                float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                    (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                int prevValue = prevKeyframe.getIntValue();
                int nextValue = nextKeyframe.getIntValue();
                // Apply interpolator on the proportional duration.
                if (interpolator != null) {
                    intervalFraction = interpolator.getInterpolation(intervalFraction);
                }
                return mEvaluator == null ?
                        prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                        ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
                                intValue();
            }
            prevKeyframe = nextKeyframe;

此時用一個例子來看就非常簡單:

ValueAnimator.ofInt(0,-20,120,40);

此時,這幾個值對應的fraction分別是:(這個可以通過上面KeyframeSet.ofInt()的源碼得到)

  • 0:0
  • -20:1/3
  • 120: 2/3
  • 40: 1
    因此,如果此時fraction爲0.5的話,1/3<0.5<2/3 那麼此時nextValue就爲120,prevValue就爲-20。

至於具體的返回值,分別需要由插值器或者mEvaluator來決定。

自定義插值器或者其他一些特殊的情況此處就不增加篇幅了。
有了代碼,邏輯也很清晰,讀者可以自己考慮下。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章