屬性動(dòng)畫機(jī)制原理解析
本文轉(zhuǎn)載自微信公眾號「Android開發(fā)編程」,作者Android開發(fā)編程 。轉(zhuǎn)載本文請聯(lián)系A(chǔ)ndroid開發(fā)編程公眾號。
前言
動(dòng)畫的使用是 Android 開發(fā)中常用的知識
可是動(dòng)畫的種類繁多、使用復(fù)雜,每當(dāng)需要采用自定義動(dòng)畫 實(shí)現(xiàn) 復(fù)雜的動(dòng)畫效果時(shí),很多開發(fā)者就顯得束手無策;
今天我們就來從源碼中分析屬性動(dòng)畫原理
一、動(dòng)畫簡單應(yīng)用
ValueAnimator
屬性動(dòng)畫的最核心的類,原理:控制值的變化,之后手動(dòng)賦值給對象的屬性,從而實(shí)現(xiàn)動(dòng)畫;
對于控制的值的不同,Android 提供給我們?nèi)N構(gòu)造方法來實(shí)例ValueAnimator對象:
ValueAnimator.ofInt(int... values) -- 整型數(shù)值
ValueAnimator.ofFloat(float... values) -- 浮點(diǎn)型數(shù)值
ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定義對象類型
1、java方式
- //設(shè)置動(dòng)畫 始 & 末值
- //ofInt()兩個(gè)作用:
- //1. 獲取實(shí)例
- //2. 在傳入?yún)?shù)之間平滑過渡
- //如下則0平滑過渡到3
- ValueAnimator animator = ValueAnimator.ofInt(0,3);
- //如下傳入多個(gè)參數(shù),效果則為0->5,5->3,3->10
- //ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);
- //設(shè)置動(dòng)畫的基礎(chǔ)屬性
- animator.setDuration(5000);//播放時(shí)長
- animator.setStartDelay(300);//延遲播放
- animator.setRepeatCount(0);//重放次數(shù)
- animator.setRepeatMode(ValueAnimator.RESTART);
- //重放模式
- //ValueAnimator.START:正序
- //ValueAnimator.REVERSE:倒序
- //設(shè)置更新監(jiān)聽
- //值 改變一次,該方法就執(zhí)行一次
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- //獲取改變后的值
- int currentValue = (int) animation.getAnimatedValue();
- //輸出改變后的值
- Log.d("test", "onAnimationUpdate: " + currentValue);
- //改變后的值發(fā)賦值給對象的屬性值
- view.setproperty(currentValue);
- //刷新視圖
- view.requestLayout();
- }
- });
- //啟動(dòng)動(dòng)畫
- animator.start();
2、 XML 方式
在路徑 res/animator/ 路徑下常見 XML 文件,如 set_animator.xml
在上述文件中設(shè)置動(dòng)畫參數(shù)
- // ValueAnimator采用<animator> 標(biāo)簽
- <animator xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="1000"
- android:valueFrom="1"
- android:valueTo="0"
- android:valueType="floatType"
- android:repeatCount="1"
- android:repeatMode="reverse"/>
- />
Java代碼啟動(dòng)動(dòng)畫
- Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);
- // 載入XML動(dòng)畫
- animator.setTarget(view);
- // 設(shè)置動(dòng)畫對象
- animator.start();
二、原理詳解
1、創(chuàng)建動(dòng)畫
- ObjectAnimator.ofFloat()開始;
- /**
- * 構(gòu)建一個(gè)返回值為 float 的 ObjectAnimator 的實(shí)例
- *
- * @param target 作用于動(dòng)畫的對象。
- * @param propertyName 屬性名稱,要求對象須有setXXX() 方法,且是 public 的。
- * @param values,屬性變化的值,可以設(shè)置 1 個(gè)或者 多個(gè)。當(dāng)只有 1 個(gè)時(shí),起始值為屬性值本身。當(dāng)有 2 個(gè)值時(shí),第 1 個(gè)為起始值,第 2 個(gè)為終止值。當(dāng)超過 2 個(gè)時(shí),首尾值的定義與 2 個(gè)時(shí)一樣,中間值做需要經(jīng)過的值。
- */
- public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
- ObjectAnimator anim = new ObjectAnimator(target, propertyName);
- anim.setFloatValues(values);
- return anim;
- }
- 創(chuàng)建一個(gè) ObjectAnimator 的實(shí)例,然后為該實(shí)例設(shè)置 values;
- 那么,繼續(xù)看 ObjectAnimator 的構(gòu)建;
構(gòu)造 ObjectaAnimator
- private ObjectAnimator(Object target, String propertyName) {
- setTarget(target);
- setPropertyName(propertyName);
- }
分別調(diào)用了 setTarget() 方法和setPropertyName();
2、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;
- }
- }
存在舊動(dòng)畫對象(也可為 null) 與新設(shè)置的動(dòng)畫對象不一致;
如果舊動(dòng)畫是開始了的狀態(tài),則先取消動(dòng)畫,然后將動(dòng)畫對象以弱引用對象為記錄下來;
3、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)有這個(gè) propertyName,則會替換其相應(yīng)的 PropertyValuesHolder,這里用了一個(gè) HashMap 來保存 propertyName 和 PropertyValuesHolder
- 如果propertyName 是 "translationX";
- 接下來看 setFloatValues() 方法;
4、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() 如下;
5、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è)構(gòu)造成 PropertyValuesHolder,最后存放在前面所說的 HashMap 里面;
- 當(dāng)然,如果這里的 hashMap 還沒有初始化,則先會將其初始化;
- 最關(guān)鍵的是要構(gòu)建出 PropertyValuesHolder 這個(gè)對象;
- 那么就繼續(xù)來看看 PropertyValuesHolder#ofFloat() 方法;
6、PropertyValuesHolder#ofFloat()
- public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
- return new FloatPropertyValuesHolder(propertyName, values);
- }
- 構(gòu)造 FloatPropertyValuesHolder;
- FloatPropertyValuesHolder
- public FloatPropertyValuesHolder(String propertyName, float... values) {
- super(propertyName);
- setFloatValues(values);
- }
- FloatPropertyValuesHolder 構(gòu)造函數(shù)比較簡單,調(diào)用父類的構(gòu)造方法并傳遞了 propertyName;
- 關(guān)鍵是進(jìn)一步 setFloatValues() 方法的調(diào)用;
- 其又進(jìn)一步調(diào)用了父類的 setFloatValues(),在父類的 setFloatValues() 方法里初始化了動(dòng)畫的關(guān)鍵幀;
- PropertyValuesHolder#setFloatValues()
- public void setFloatValues(float... values) {
- mValueType = float.class;
- mKeyframes = KeyframeSet.ofFloat(values);
- }
- 進(jìn)一步調(diào)用了 KeyframeSet#ofFloat() 方法以完成關(guān)鍵幀的構(gòu)造;
- KeyframeSet 是接口 Keyframe 的實(shí)現(xiàn)類;
7、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 個(gè)重要的參數(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)鍵幀 匯集到一個(gè)集合中
- return new FloatKeyframeSet(keyframes);
- }
其主要內(nèi)容是:
- 構(gòu)造動(dòng)畫的關(guān)鍵幀,且動(dòng)畫里至少要有 2 個(gè)關(guān)鍵幀;
- 關(guān)鍵幀中有 2 個(gè)重要的參數(shù),fraction這個(gè)可以看成是關(guān)鍵幀的序號,value 關(guān)鍵幀的值,可能是起始值,也可能是中間的某個(gè)值;
- 最后將關(guān)鍵幀匯集成一個(gè)關(guān)鍵幀集返回給 PropertyValuesHolder;
8、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();
9、setInterpolator()
- @Override
- public void setInterpolator(TimeInterpolator value) {
- if (value != null) {
- mInterpolator = value;
- } else {
- mInterpolator = new LinearInterpolator();
- }
- }
傳遞的是 null 的話,則默認(rèn)使用的便是 LinearInterpolator,即線性插值器;
我們這里的假設(shè)的場景也是設(shè)置了 LinearInterpolator,這是最簡單的插值器,其作用就是完成勻速運(yùn)動(dòng);
10、 LinearInterpolator粗略分析;
- /**
- * 插值器定義了動(dòng)畫變化的頻率,其可以是線性的也可以是非線性的,如加速運(yùn)動(dòng)或者減速運(yùn)動(dòng);
- */
- public interface TimeInterpolator {
- /**
- * 這里傳進(jìn)來的 input 代表當(dāng)前時(shí)間與總時(shí)間的比,根據(jù)這個(gè)時(shí)間占比返回當(dāng)前的變化頻率。其輸出與輸值都在 [0,1] 之間
- */
- float getInterpolation(float input);
- }
插值器的關(guān)鍵定義便是實(shí)現(xiàn) getInterpolation() 方法,即根據(jù)當(dāng)前動(dòng)畫運(yùn)行的時(shí)間占比來計(jì)算當(dāng)前動(dòng)畫的變化頻率;
那么來看看 LinearInterpolator 的 getInterpolation() 實(shí)現(xiàn);
- LinearInterpolator#getInterpolation()
- public float getInterpolation(float input) {
- return input;
- }
對,就是返回原值,因?yàn)闀r(shí)間的變化肯定始終都是勻速的;
11、start
啟動(dòng)動(dòng)畫從 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();
- }
- 先確認(rèn)動(dòng)畫已經(jīng)取消;這個(gè)方法里的重要的那句代碼就是調(diào)用父類 ValueAnimator 的 start();
- 父類對外的 start() 方法很簡單,其主要的實(shí)現(xiàn)在另一個(gè)重載的私有 start() 方法上;
- 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);
- }
- }
- }
其中之一是 addAnimationCallback(),其主要是向 AnimationHander 添加一個(gè)回調(diào)接口AnimationHandler.AnimationFrameCallback,如下代碼;
- /**
- * 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 就實(shí)現(xiàn)了 AnimationFrameCallback,所以這里添加的是 ValueAnimator 的實(shí)例;
- 最終被添加到 mAnimationCallbacks 這個(gè)隊(duì)列中;
12、startAnimation()
- private void startAnimation() {
- ......
- mAnimationEndRequested = false;
- initAnimation();
- mRunning = true;
- if (mSeekFraction >= 0) {
- mOverallFraction = mSeekFraction;
- } else {
- mOverallFraction = 0f;
- }
- if (mListeners != null) {
- // 通過動(dòng)畫監(jiān)聽器動(dòng)畫開始了
- 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() 方法,所以這里默認(rèn)給的就是 FloatEvaluator;
- 接下來就會進(jìn)一步調(diào)用 setCurrentPlayTime() 來開始動(dòng)畫;
13、setCurrentPlayTime()
- public void setCurrentPlayTime(long playTime) {
- float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;
- setCurrentFraction(fraction);
- }
- 初始時(shí)調(diào)用的是setCurrentPlayTime(0),也就是 playTime 為 0,而 mDuration 就是我們自己通過 setDuration() 來設(shè)置的;
- 所以這里得到的 fraction 也是 0;
- 進(jìn)一步看 setCurrentFraction() 方法;
14、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()) {
- // 隨機(jī)時(shí)間?
- long seekTime = (long) (getScaledDuration() * fraction);
- // 獲取動(dòng)畫的當(dāng)前運(yùn)行時(shí)間
- 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.
- // 得到開始時(shí)間
- 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í)行動(dòng)畫,注意這里會先調(diào)用子類的 animateValue() 方法
- animateValue(currentIterationFraction);
- }
前面都是一些時(shí)間的計(jì)算,得到當(dāng)前真正的currentIterationFraction,最后會通過調(diào)用animateValue() 來執(zhí)行動(dòng)畫;
15、 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() ,這個(gè)很關(guān)鍵,時(shí)間插值與估值器的計(jì)算都在父類的 animateValue() 方法中進(jìn)行的。
- 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) {
- // 獲取時(shí)間插值
- fraction = mInterpolator.getInterpolation(fraction);
- mCurrentFraction = fraction;
- int numValues = mValues.length;
- // 將時(shí)間插值送給估值器,計(jì)算出 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ì)算時(shí)間插值和估值器、調(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 方法來修改屬性的;
- 分析到這里,就完成了動(dòng)畫的一幀關(guān)鍵幀的執(zhí)行;
- 剩下的幀是怎么驅(qū)動(dòng)的呢?還是得回到 start() 方法里面,在這里最初分析到 addAnimationFrameCallback() 方法;
- 這個(gè)方法里等于是向AnimationHandler注冊了AnimationHandler.AnimationFrameCallback;
- 這個(gè) callback 中其中之一的方法是 doAnimationFrame();
在 ValueAnimator 的實(shí)現(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;
- }
- 目的也還是計(jì)算出 currentIterationFraction;
- 通過 animateValue() 方法來執(zhí)行動(dòng)畫;
- 可以看到只要 doAnimationFrame() 被不斷的調(diào)用,就會產(chǎn)生動(dòng)畫的一個(gè)關(guān)鍵幀;
- 如果關(guān)鍵幀是連續(xù)的,那么最后也就產(chǎn)生了我們所看到的動(dòng)畫;
- 再來分析doAnimationFrame() 是如何被不斷調(diào)用的;
- 這個(gè)需要回到 AnimationHandler 中來,在 AnimationHandler 中有一個(gè)非常重要的 callback 實(shí)現(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);
- }
- }
- };
- Andorid 中的重繪就是由Choreographer在 1 秒內(nèi)產(chǎn)生 60 個(gè) vsync 來通知 view tree 進(jìn)行 view 的重繪的;
- 而 vsync 產(chǎn)生后會調(diào)用它的監(jiān)聽者回調(diào)接口 Choreographer.FrameCallback,;
- 也就是說,只要向Choreographer注冊了這個(gè)接口,就會每 1 秒里收到 60 次回調(diào);
- 因此,在這里就實(shí)現(xiàn)了不斷地調(diào)用 doAnimationFrame() 來驅(qū)動(dòng)動(dòng)畫了;
16、流程總結(jié)
- 動(dòng)畫是由許多的關(guān)鍵幀組成的;
- 屬性動(dòng)畫的主要組成是 PropertyValuesHolder,而 PropertyValuesHolder 又封裝了關(guān)鍵幀;
- 動(dòng)畫開始后,其監(jiān)聽了 Choreographer 的 vsync,使得其可以不斷地調(diào)用 doAnimationFrame() 來驅(qū)動(dòng)動(dòng)畫執(zhí)行每一個(gè)關(guān)鍵幀;
- 每一次的 doAnimationFrame() 調(diào)用都會去計(jì)算時(shí)間插值,而通過時(shí)間插值器計(jì)算得到 fraction 又會傳給估值器,使得估值器可以計(jì)算出屬性的當(dāng)前值;
- 最后再通過 PropertyValuesHolder 所記錄下的 Setter 方法,以反射的方式來修改目標(biāo)屬性的值;
- 當(dāng)屬性值一幀一幀的改變后,形成連續(xù)后,便是我們所見到的動(dòng)畫;
總結(jié)
2021年最后一個(gè)月,大家一起加油努力;