首頁 > 軟體

Android深入分析屬性動畫原始碼

2022-08-26 14:01:50

1.先看一段動畫的程式碼實現

ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);
alpha.setDuration(500);
alpha.start();

程式碼很簡單,上面三行程式碼就可以開啟一個透明度變化的動畫。 那麼android系統到底是如何實現的呢?進入原始碼分析。

1)看第一行程式碼:

ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);

建立了一個ObjectAnimator物件,並把values陣列設定給了anim物件。

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
}

ObjectAnimator 建構函式中。將傳過來的View物件和propertyName賦值給成員變數。

private ObjectAnimator(Object target, String propertyName) {
        //將傳過來的View物件賦值給成員變數mTarget
        setTarget(target);
        //將propertyName賦值給成員變數mPropertyName
        setPropertyName(propertyName);
}

注意這個mTarget為什麼要用一個軟參照?

那是為了防止Activity發生記憶體漏失。因為會有Activity已經退出,但是動畫可能還未執行完,這個時候View得不到釋放的話,會引發Activity記憶體漏失。

private WeakReference<Object> mTarget;
public void setTarget(@Nullable Object target) {
    final Object oldTarget = getTarget();
    if (oldTarget != target) {
        if (isStarted()) {
            cancel();
        }
        //將傳進來的View物件賦值給mTarget
        mTarget = target == null ? null : new WeakReference<Object>(target);
        mInitialized = false;
    }
}

再看第二行程式碼做了啥?anim.setFloatValues(values);

首次進來mValues==null,mProperty==null,所以會執行這行程式碼。 setValues(PropertyValuesHolder.ofFloat(mPropertyName, values))。

public void setFloatValues(float... values) {
    if (mValues == null || mValues.length == 0) {
        if (mProperty != null) {
            setValues(PropertyValuesHolder.ofFloat(mProperty, values));
        } else {
            setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
        }
    } else {
        super.setFloatValues(values);
    }
}

setValue將得到的 PropertyValuesHolder陣列賦值給成員變數PropertyValuesHolder[] mValues;

再看PropertyValuesHolder.ofFloat(mPropertyName, values));

先呼叫super建構函式,將propertyName賦值給父類別的mPropertyName,

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

然後再呼叫setFloatValues(values);

public void setFloatValues(float... values) {
    super.setFloatValues(values);
    //將mKeyframes強轉為mFloatKeyframes
    mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
//呼叫父類別方法建立了KeyframeSet物件,賦值給了mKeyframes
public void setFloatValues(float... values) {
    mValueType = float.class;
    mKeyframes = KeyframeSet.ofFloat(values);
}

KeyframeSet.ofFloat(values);這行程式碼建立了一個關鍵幀的集合。

public static KeyframeSet ofFloat(float... values) {
    boolean badValue = false;
    int numKeyframes = values.length;
    //建立一個value長度的 FloatKeyFrame的陣列
    FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
    //numKeyframes==1的話,其實是沒有View是沒有動畫的。如果傳過來的values的長度是1的話,會報錯的。
    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 {
        //下面的程式碼才是關鍵的 Keyframe ofFloat(float fraction, float value)是建立關鍵幀。
        //fraction英文單詞意思是部分,在這作為引數的意思是:從動畫啟示位置,到當前位置,所佔的整個動畫的百分比。
        //value就是某個部分對應的屬性值。
        // 比如傳進來的value值是1.0f 2.0f 3.0f 4.0f,5.0f。整個動畫有5個值。因為1.0是初始值,要完成整個動畫需要4步。
         //從1-2,2-3,3-4,4-5;4個部分。
         //第0個位置是起始位置,所以他所在的部分就是0。第一個位置就是四分之一,第二個就是四分之二....
         //第i個位置,所在整個動畫的部分就是i/(i-1)。而這個位置對應的動畫的屬性值,就是value[i]
        //所以這個keyframes[]陣列的目的就是儲存,動畫的關鍵位置所佔的百分比和關鍵位置對應的屬性值。
        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;
            }
        }
    }
    return new FloatKeyframeSet(keyframes);
}

到這為止,第一行程式碼執行完畢。

ObjectAnimator.ofFloat(view, "alpha", 1, 0,1)

將view賦值給ObjectAnimator成員變數。

將propertyName賦值給PropertyValuesHolder,會通過屬性name來反射它的set方法,用來修改屬性值。

建立KeyframeSet,關鍵幀集合。將value陣列轉換成對應的關鍵幀集合,通過動畫執行的時間,來計算當前時間對應的屬性值,然後再呼叫view的set屬性方法,從而達到形成動畫的目的。

這塊的程式碼會再後面看到。

2).看動畫的第二行程式碼alpha.start();

ObjectAnimator的父類別是ValueAnimator。start()裡面呼叫到的方法會在子類和父類別裡跳來跳去,這也增大了閱讀的難度。

首先看ValueAnimator#start(boolean playBackwards)方法

addAnimationCallback:向Choreographer註冊回撥函數,我們知道Choreographer可以接受Vsync訊號,16.66ms一次,也是螢幕重新整理一次的時間。這樣在螢幕重新整理的時候,就可以通過向Choreographer註冊回撥函數進行動畫的更新。

 private void start(boolean playBackwards) {
        //Animators 必須執行在一個Looper不能為空的執行緒中,因為動畫需要涉及到Choreographer。
        if (Looper.myLooper() == null) {
            throw new AndroidRuntimeException("Animators may only be run on Looper threads");
        }
        mStarted = true;
        mPaused = false;
        mRunning = false;
        mAnimationEndRequested = false;
        mStartTime = -1;
        //這個是一個回撥函數。這塊是由Choreographer回撥的,稍後分析。
        addAnimationCallback(0);
        if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
            //開始動畫。
            startAnimation();
        }
    }

先看startAnimation方法(),會在這個方法中呼叫initAnimation();

在這會先呼叫子類ObjectAnimator,然後在呼叫父類別的ValueAnimator的initAnimation方法。

先看子類的initAnimation(),這個方法根據propertyName來反射view的set屬性方法。

void initAnimation() {
     if (!mInitialized) {
        //先拿到target,也就是view物件。
         final Object target = getTarget();
         if (target != null) {
         // PropertyValuesHolder[] mValues;這個values就是PropertyValuesHolder的集合。
             final int numValues = mValues.length;
             for (int i = 0; i < numValues; ++i) {
                //在PropertyValuesHolder中傳進了屬性值,下面這行程式碼就是根據屬性值,來反射view的set方法,
                //通過set方法,就可以動態的改變view的屬性值的變化。
                 mValues[i].setupSetterAndGetter(target);
             }
         }
         //呼叫父類別的initAnimation()方法
         super.initAnimation();
     }
}

再看父類別ValueAnimator的initAnimation方法。呼叫了PropertyValuesHolder的init()方法。

在init方法中,向KeyframeSet關鍵幀集合設定了一個估值器,這個用來計算屬性值的,後面會看到具體的計算方法。

void initAnimation() {
    if (!mInitialized) {
        int numValues = mValues.length;
        for (int i = 0; i < numValues; ++i) {
        //呼叫PropertyValuesHolder#init方法
            mValues[i].init();
        }
        mInitialized = true;
    }
}
 void init() {
    if (mEvaluator == null) {
        //得到一個估值器
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
                (mValueType == Float.class) ? sFloatEvaluator : null;
    }
    if (mEvaluator != null) {
        //向KeyframeSet中設定一個估值器,這個估值器用來計算動畫在某個時刻的屬性值。
        mKeyframes.setEvaluator(mEvaluator);
    }
}
private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();
public class FloatEvaluator implements TypeEvaluator<Number> {
    //This function returns the result of linearly interpolating the start and end values
    這個方法返回一個在動畫開始和結束之間的一個線性的結果。其實就是個一元一次方程,來計算動畫當前的位置。
    //result = x0 + t * (v1 - v0)
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

至此,initAnimation的程式碼已經執行完畢。主要做的工作可以總結為兩點:

1.呼叫PropertyValuesHolder的setupSetterAndGetter方法,通過反射拿到View的setter方法。

2.向KeyframeSet中設定一個估值器,用來計算動畫某一時刻的屬性值。

3)接下來看ValueAnimator#addAnimationCallback

這個方法是向Choreographer設定了一個會回撥函數,每隔16.66ms回撥一次,用來重新整理動畫。

還設定了一個回撥集合,在Choreographer的回撥函數中,回撥集合裡面的回撥函數,來實現屬性動畫的重新整理

private void addAnimationCallback(long delay) {
    if (!mSelfPulse) {
        return;
    }
    //getAnimationHandler 就是上面建立的AnimationHandler。
    //將this作為 AnimationFrameCallback的回撥,會回撥doAnimationFrame(long frameTime)
    getAnimationHandler().addAnimationFrameCallback(this, delay);
}
//AnimationHandler#addAnimationFrameCallback
getProvider()拿到的是MyFrameCallbackProvider。
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
    if (mAnimationCallbacks.size() == 0) {
        //向Choreographer加入一個回撥函數mFrameCallback
        getProvider().postFrameCallback(mFrameCallback);
    }
    //將新增的回撥函數加入一個回撥的集合。
    if (!mAnimationCallbacks.contains(callback)) {
        mAnimationCallbacks.add(callback);
    }
}

先看這個getProvider().postFrameCallback(mFrameCallback);這個就是向Choreographer註冊一個回撥。

final Choreographer mChoreographer = Choreographer.getInstance();
    //這行程式碼是向編舞者Choreographer新增了一個回撥函數。
    public void postFrameCallback(Choreographer.FrameCallback callback) {
        mChoreographer.postFrameCallback(callback);
}
Choreographer中
public void postFrameCallback(FrameCallback callback) {
        postFrameCallbackDelayed(callback, 0);
}

下面這行程式碼就是向Choreographer新增CallBackType為CALLBACK_ANIMATION,Token為FRAME_CALLBACK_TOKEN的回撥函數。 callback 就是傳進來的mFrameCallback。

public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
    postCallbackDelayedInternal(CALLBACK_ANIMATION,
            callback, FRAME_CALLBACK_TOKEN, delayMillis);
}

省略中間的呼叫過程。。。這塊的程式碼在Choreographer原始碼分析過。

MyFrameCallbackProvider#postFrameCallback就是向Choreographer新增一個回撥函數。 我們知道,Choreographer在接收到Vsync訊號後呼叫這些回撥函數。

 void doFrame(long frameTimeNanos, int frame) {
 doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
    doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
 }
最終會調到這裡,根據上面傳過來的token,轉換成不同的回撥函數,呼叫不同的方法。
//在將View繪製時,呼叫的是else分支的回撥
//在動畫這裡,傳進來的是mFrameCallback,Choreographer.FrameCallback的範例,會呼叫到doFrame方法
 public void run(long frameTimeNanos) {
    if (token == FRAME_CALLBACK_TOKEN) {
        ((FrameCallback)action).doFrame(frameTimeNanos);
    } else {
        ((Runnable)action).run();
    }
}
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
    @Override
    public void doFrame(long frameTimeNanos) {
        doAnimationFrame(getProvider().getFrameTime());
        if (mAnimationCallbacks.size() > 0) {
            //再次向Choreographer註冊回撥,等到下一次Vsync訊號來的時候呼叫,
            //針對於60Hz的螢幕,重新整理時間間隔是16.66ms,也就是Vsync回撥的時間間隔
            //也就是說屬性動畫16.66毫秒會改變一次
            getProvider().postFrameCallback(this);
        }
    }
};

Choreographer中每個16.6ms會回撥doFrame方法(),在doAnimationFrame方法中,就會回撥註冊的回撥集合。

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;
        }
        //遍歷mAnimationCallbacks,呼叫callBack回撥函數,
        //這個回撥函數是ValueAnimator的doAnimationFrame
        if (isCallbackDue(callback, currentTime)) {
            callback.doAnimationFrame(frameTime);
        }
    }
}

doAnimationFrame是AnimationFrameCallback的回撥函數,由ValueAnimator實現。

 public final boolean doAnimationFrame(long frameTime) {
       //frameTime 這個時間是從Choreographer傳過來的時間,
       //記錄為上一次動畫重新整理的時間
        mLastFrameTime = frameTime;
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        return finished;
 }
 public final boolean doAnimationFrame(long frameTime) {
       //frameTime 這個時間是從Choreographer傳過來的時間,
       //記錄為上一次動畫重新整理的時間
        mLastFrameTime = frameTime;
        final long currentTime = Math.max(frameTime, mStartTime);
        boolean finished = animateBasedOnTime(currentTime);
        return finished;
 }
boolean animateBasedOnTime(long currentTime) {
     boolean done = false;
     if (mRunning) {
        //拿到總時間
         final long scaledDuration = getScaledDuration();
         //通過計算得到動畫當前執行佔比多少。(currentTime - mStartTime)動畫執行的時間
         //除以scaledDuration總時間,得到就是已經執行的部分,如果是一個重複的動畫,這個值可能會大於1.
         final float fraction = scaledDuration > 0 ?
                 (float)(currentTime - mStartTime) / scaledDuration : 1f;
        //下面通過計算對fraction進行修正,減去重複執行的部分,得到真正的在一次動畫中要執行到哪一部分
         mOverallFraction = clampFraction(fraction);
         float currentIterationFraction = getCurrentIterationFraction(
                 mOverallFraction, mReversing);
         animateValue(currentIterationFraction);
     }
     return done;
 }

注意animateValue,這個方法在父類別ValueAnimator和子類ObjectAnimator都有實現。

所以這裡先呼叫子類ObjectAnimator的方法。

//這個方法是呼叫的子類的方法
void animateValue(float fraction) {
     final Object target = getTarget();
     if (mTarget != null && target == null) {
         cancel();
         return;
     }
     //先呼叫父類別的方法
     super.animateValue(fraction);
     //再回到子類
     int numValues = mValues.length;
     for (int i = 0; i < numValues; ++i) {
        //給View設定改變後的屬性值     
         mValues[i].setAnimatedValue(target);
     }
 }

先看super.animateValue方法,這個方法就是去計算動畫變動後的屬性值。

 void animateValue(float fraction) {
     //通過插值器,來修改。如果沒有設定插值器,那麼fraction的變化就是勻速的。
     //經過插值器的計算,fraction的變化就會呈現出加速、減速變化的效果。
     fraction = mInterpolator.getInterpolation(fraction);
     mCurrentFraction = fraction;
     int numValues = mValues.length;
     for (int i = 0; i < numValues; ++i) {
         //PropertyValuesHolder[] mValues,因為一個View可以有多個屬性動畫,所以這用一個陣列來儲存。
         mValues[i].calculateValue(fraction);
     }
 }
AccelerateDecelerateInterpolator 插值器
public float getInterpolation(float input) {
    return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
void calculateValue(float fraction) {
    //mKeyframes 就是前面建立的關鍵幀集合KeyframeSet
    Object value = mKeyframes.getValue(fraction);
   // 將得到的值,賦值給mAnimatedValue
    mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}

下面這個方法是真正去計算改變後的屬性值。通過估值器mEvaluator去計算的。

public Object getValue(float fraction) {
    //第一關鍵幀記做前一關鍵幀
    Keyframe prevKeyframe = mFirstKeyframe;
    for (int i = 1; i < mNumKeyframes; ++i) {
        //得到下一關鍵幀
        Keyframe nextKeyframe = mKeyframes.get(i);
        if (fraction < nextKeyframe.getFraction()) {
            final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
            //得到前一關鍵幀,對應的部分
            final float prevFraction = prevKeyframe.getFraction();
            //fraction - prevFraction 當前要執行的部分距離前一關鍵幀是多少。
            //nextKeyframe.getFraction() - prevFraction,這一幀有多少
            //兩者相除,得到的就是當前部分在這一幀的佔比
            float intervalFraction = (fraction - prevFraction) /
                (nextKeyframe.getFraction() - prevFraction);
            if (interpolator != null) {
                //通過插值器來修改,這一部分的大小
                intervalFraction = interpolator.getInterpolation(intervalFraction);
            }
            //通過估值器,來計算屬性值要變化到多少
            //這個估值器就是上面賦值的FloatEvaluator或IntEvaluator
            return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                    nextKeyframe.getValue());
        }
        prevKeyframe = nextKeyframe;
    }
    // shouldn't reach here
    //不應該執行到這裡,在上面的for迴圈就應該返回當前動畫,屬性變化的大小。
    return mLastKeyframe.getValue();
}

通過估值器計算view的屬性值。

public Float evaluate(float fraction, Number startValue, Number endValue) {
    float startFloat = startValue.floatValue();
    //通過一個一元一次方程,來計算得到當前的屬性值。
    return startFloat + fraction * (endValue.floatValue() - startFloat);
}

至此,動畫要變動後的屬性值,已經計算出來了,

通過 mValues[i].setAnimatedValue(target);用來修改View的屬性值大小。

 void setAnimatedValue(Object target) {
       //前面已經通過反射拿到了View的setter方法
      if (mSetter != null) {
          try {
               //拿到屬性值大小,          
              mTmpValueArray[0] = getAnimatedValue();
             //通過反射,修改view屬性值的大小
              mSetter.invoke(target, mTmpValueArray);
          } catch (InvocationTargetException e) {
              Log.e("PropertyValuesHolder", e.toString());
          } catch (IllegalAccessException e) {
              Log.e("PropertyValuesHolder", e.toString());
          }
      }
  }
Object getAnimatedValue() {
    return mAnimatedValue;
}

至此,android屬性動畫的整個執行流程已經分析完畢。

可以總結以下幾點:

1.ValueAnimator是父類別,ObjectAnimator是子類,這裡面封裝了一個target,也就是view物件。

2.PropertyValuesHolder,有屬性名,屬性值,通過屬名來反射view的setter方法,來動態修改屬性值。

3.KeyframeSet,是一個關鍵幀集合,封裝了定義動畫是value陣列的值,每一個值都被記錄為一個關鍵幀FloatKeyframe。

4.通過插值器,可以改變屬性變化的快慢,通過估值器計算屬性值的大小。

5.給Choreographer註冊了一個回撥,每隔16.66ms回撥一次,每一次回撥都會去改變view屬性值的大小。改變是通過fraction計算的,進而通過計算得到改變後的屬性值大小。

這樣動態的改變view屬性值的大小,就連貫的形成一幅動畫。

到此這篇關於Android深入分析屬性動畫原始碼的文章就介紹到這了,更多相關Android屬性動畫內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com