導(dǎo)讀
- 移動開發(fā)知識體系總章(Java基礎(chǔ)拆内、Android、Flutter)
- Android 動畫的分類及介紹
- Android中的視圖動畫(View Animation)(View動畫厨喂、補(bǔ)間動畫)
- Android中的逐幀動畫(Drawable Animation)
- Android中的基礎(chǔ)動畫 屬性動畫(Property Animation)
Android中的基礎(chǔ)動畫 屬性動畫(Property Animation)
通過前面的文章Android中的視圖動畫(View Animation)(View動畫贷笛、補(bǔ)間動畫)我們知道視圖動畫只是改變了View的視覺效果靶庙,而實(shí)際并未變更,而屬性動畫可謂是視圖動畫的加強(qiáng)版艾栋,并且具有更好的特性爆存,因為屬性動畫不僅改變了視覺效果,而且實(shí)際也跟隨變動了蝗砾,并保留了視圖動畫如監(jiān)聽等功能先较。
舉個不恰當(dāng)?shù)睦樱?br> 彭空空做夢賺了一卡車的人民幣,實(shí)際收入呢遥诉,0元拇泣;
馬云大佬做夢賺了一卡車的人民幣,實(shí)際收入一卡車的人民幣。
這里我寫了兩段簡單的代碼矮锈,一個是屬性動畫霉翔,一個是補(bǔ)間動畫,效果均是讓Button平移的效果苞笨,并對兩個Button設(shè)置了點(diǎn)擊事件债朵,以下是關(guān)鍵代碼:
private void showTweenAnim() {
Animation translateAnimation = new TranslateAnimation(0, 800, 0, 0);
translateAnimation.setDuration(3000);
translateAnimation.setRepeatCount(-1);
button2.startAnimation(translateAnimation);
}
private void showObjectAnimatorOfFloat() {
ObjectAnimator animator = ObjectAnimator.ofFloat(button1, "translationX", 0, 500);
animator.setDuration(3000);
animator.setRepeatCount(-1);
animator.start();
}
這是以上代碼的動畫效果和點(diǎn)擊事件的響應(yīng),可以看到Button從左邊移動到右邊瀑凝,屬性動畫一直可以響應(yīng)點(diǎn)擊事件序芦,而補(bǔ)間動畫只有在原來的位置才響應(yīng)事件。
再看兩個動畫的代碼粤咪,對比發(fā)現(xiàn)谚中,除了構(gòu)造(靜態(tài)方法)基本上一致,而我們知道補(bǔ)間動畫要實(shí)現(xiàn)平移、選擇宪塔、縮放磁奖、透明度的動畫,分別需要TranslateAnimation(平移動畫)某筐、RotateAnimation(旋轉(zhuǎn)動畫)比搭、ScaleAnimation(縮放動畫)、AlphaAnimation(透明度動畫)這些類南誊,而上面的代碼中身诺,屬性動畫使用了ObjectAnimator的ofFloat()方法,后面?zhèn)魅腙P(guān)鍵參數(shù)“translationX”抄囚,就實(shí)現(xiàn)了平移動畫霉赡,看來屬性動畫內(nèi)部進(jìn)行了擴(kuò)展性的封裝,這里就不去具體研究是如何封裝的了怠苔。
下面具體來看看ObjectAnimator.ofFloat()方法:
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setIntValues(values);
return anim;
}
- new ObjectAnimator(target, propertyName)
ofInt(Object target, String propertyName, int... values)方法接收三個參數(shù)同廉。先把前兩個參數(shù)傳遞給了ObjectAnimator構(gòu)造方法:
構(gòu)造方法里先是執(zhí)行了setTarget(target)方法,根據(jù)命名可以看出是進(jìn)行綁定的操作的柑司,內(nèi)部進(jìn)行了一個oldTarget的比對,并且內(nèi)部使用到了弱引用锅劝,這里貼出代碼來:private ObjectAnimator(Object target, String propertyName) { setTarget(target); setPropertyName(propertyName); }
@Override 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; } }
引用類型是java中比較重要的概念攒驰,可查看Java引用類型
- setPropertyName(propertyName)方法:
這里這里先對mValues進(jìn)行了非空判斷,如果不為空故爵,就會把mValues的第一個參數(shù)取出來作為PropertyValuesHolder玻粪,并綁定propertyName,再存儲到mValuesMap诬垂,進(jìn)行了存儲劲室,那么PropertyValuesHolder是什么呢: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; }
/**
* This class holds information about a property and the values that that property
* should take on during an animation. PropertyValuesHolder objects can be used to create
* animations with ValueAnimator or ObjectAnimator that operate on several different properties
* in parallel.
*/
public class PropertyValuesHolder implements Cloneable {...}
通過注釋,我們得知PropertyValuesHolder是用來封裝屬性相關(guān)的變量:
- setFloatValues(float... values)方法:
以及父類ValueAnimator的setFloatValues(float... values)方法:@Override public void setFloatValues(float... values) { if (mValues == null || mValues.length == 0) { // No values yet - this animator is being constructed piecemeal. Init the values with // whatever the current propertyName is if (mProperty != null) { setValues(PropertyValuesHolder.ofFloat(mProperty, values)); } else { setValues(PropertyValuesHolder.ofFloat(mPropertyName, values)); } } else { super.setFloatValues(values); } }
關(guān)注兩個方法: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; }
- PropertyValuesHolder.ofFloat("", values)
可以看到结窘,子類和父類的setFloatValues(float... values)方法很洋,最終都會執(zhí)行setValues方法,這里先追蹤PropertyValuesHolder.ofFloat()隧枫,由于跳轉(zhuǎn)較多喉磁,這里貼出最終的核心代碼:(Keyframes接口的實(shí)現(xiàn)類KeyframeSet中的一段核心代碼)
這段代碼是要把傳遞過來的values進(jìn)行FloatKeyframe轉(zhuǎn)換為2個幀,這就是前面說到的開始幀(開始狀態(tài))官脓、和結(jié)束幀(結(jié)束狀態(tài))协怒。public static KeyframeSet ofFloat(float... values) { boolean badValue = false; int numKeyframes = values.length; FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)]; 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"); } return new FloatKeyframeSet(keyframes); }
- setValues(PropertyValuesHolder... values)
最終是把values的所有參數(shù)取出來,作為PropertyValuesHolder存儲到了mValuesMap中卑笨。public void setValues(PropertyValuesHolder... values) { int numValues = values.length; mValues = values; mValuesMap = new HashMap<String, PropertyValuesHolder>(numValues); for (int i = 0; i < numValues; ++i) { PropertyValuesHolder valuesHolder = values[i]; mValuesMap.put(valuesHolder.getPropertyName(), valuesHolder); } // New property/values/target should cause re-initialization prior to starting mInitialized = false; }
- PropertyValuesHolder.ofFloat("", values)
到這里孕暇,準(zhǔn)備工作就做完了,即為target準(zhǔn)備propertyName的動畫,把傳遞過來的values轉(zhuǎn)換為系統(tǒng)識別的開始幀妖滔、結(jié)束幀隧哮。
setDuration()、setRepeatCount()铛楣、就不展開了近迁,主要看看start()方法:
@Override
public void start() {
AnimationHandler.getInstance().autoCancelBasedOn(this);
if (DBG) {...}
super.start();
}
新出現(xiàn)一個以Handler命名的類AnimationHandler,由于后面是getInstance()方法簸州,我們大膽猜測這是一個單例鉴竭,單列模式是編程中常見的設(shè)計模式,可查看單例的相關(guān)知識岸浑。跟著后面的autoCancelBasedOn()顧明思意應(yīng)該就是用于動畫取消搏存,保證即將執(zhí)行的動畫的唯一性,這里也不展開了矢洲,先看看AnimationHandler的定義:
/**
* This custom, static handler handles the timing pulse that is shared by all active
* ValueAnimators. This approach ensures that the setting of animation values will happen on the
* same thread that animations start on, and that all animations will share the same times for
* calculating their values, which makes synchronizing animations possible.
*
* The handler uses the Choreographer by default for doing periodic callbacks. A custom
* AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
* may be independent of UI frame update. This could be useful in testing.
*
* @hide
*/
public class AnimationHandler {}
大概意思是說AnimationHandler主要是用于處理所有活動的屬性動畫共享的“時間脈沖”璧眠,這個時間脈沖即從開始到結(jié)束每個時間段的“值”,AnimationHandler保證了一個動畫的完整播放都是發(fā)生在同一個線程读虏,該處理程序默認(rèn)情況下使用
Choreographer
進(jìn)行定期回調(diào)责静。 可以在處理程序上設(shè)置自定義AnimationFrameCallbackProvider
,以提供可能獨(dú)立于UI框架更新的定時脈沖盖桥。由于該類非常重要灾螃,所以后面還會涉及到該類下的其他方法。
知道了AnimationHandler具有重要的管理的作用后揩徊,繼續(xù)追蹤父類ValueAnimator
的start()方法:
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
...
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);
}
}
}
在start()方法中關(guān)注這些:
對Looper的非空判斷
Looper是Android中非常重要的類腰鬼,可查看有關(guān)Looper的相關(guān)文章,-
addAnimationCallback(0)
private void addAnimationCallback(long delay) { ... getAnimationHandler().addAnimationFrameCallback(this, delay); }
這里拿到了具有管理功能的AnimationHandler單例對象塑荒,并且前面說到AnimationHandler是很重要的類熄赡,那么這里深入看看這里添加的回調(diào):
/** * 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); } ... }
方法注釋說,注冊以獲取延遲后下一幀的回調(diào)齿税,然后方法中執(zhí)行了postFrameCallback(mFrameCallback)方法彼硫,這里重點(diǎn)關(guān)注mFrameCallback:
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() { @Override public void doFrame(long frameTimeNanos) { doAnimationFrame(getProvider().getFrameTime()); if (mAnimationCallbacks.size() > 0) { getProvider().postFrameCallback(this); } } };
這里有出現(xiàn)了前面提到的類:Choreographer
/** * Coordinates the timing of animations, input and drawing. *... */ public final class Choreographer { /** * Implement this interface to receive a callback when a new display frame is * being rendered. The callback is invoked on the {@link Looper} thread to * which the {@link Choreographer} is attached. */ public interface FrameCallback { /** * Called when a new display frame is being rendered. * ... */ public void doFrame(long frameTimeNanos); } }
由于注釋都很長,這里貼出關(guān)鍵注釋和其含義:
- class Choreographer :*協(xié)調(diào)動畫偎窘,輸入和繪圖的時間乌助。
- interface FrameCallback :實(shí)現(xiàn)此接口以在呈現(xiàn)新的顯示框架時接收回調(diào)。
- void doFrame :在渲染新的顯示框架時調(diào)用陌知。
Choreographer翻譯過來是“編舞”的意思他托,舞蹈其實(shí)就是一個動作一個動作的組合,而動畫也是一幀一幀的組合仆葡,而通過上面的注釋來看赏参,即在動畫中每次渲染的時候Choreographer都會執(zhí)行FrameCallback接口的doFrame志笼,似乎確實(shí)有“編舞”之意。既然如此把篓,我們來驗證一下是不是每次渲染時都要調(diào)用該回調(diào):
1纫溃、以Debug模式運(yùn)行
2、在start()方法上打上斷點(diǎn)
3韧掩、在動畫中添加addUpdateListener監(jiān)聽紊浩,并打上斷點(diǎn)
4、在AnimationHandler類下的mFrameCallback中打上斷點(diǎn)
5疗锐、點(diǎn)擊執(zhí)行動畫
6坊谁、通過點(diǎn)擊resume跳到下一個斷點(diǎn),
調(diào)試1.png
調(diào)試2.png
這里需要注意 doAnimationFrame 的斷點(diǎn)滑臊,必須要在后面打上口芍,而不是一開始打上
通過debug我們會發(fā)現(xiàn),doAnimationFrame之后就會調(diào)用addUpdateListener雇卷,然后重復(fù)如此鬓椭,一直到動畫結(jié)束,并且越簡單的動畫关划,重復(fù)次數(shù)越少悄蕾,反之則重復(fù)次數(shù)越多片任,這里可以通過setDuration(30)和setDuration(3000)進(jìn)行對比熟史。
整個流程就是:通過getAnimationHandler().addAnimationFrameCallback(this, delay)進(jìn)行回調(diào)綁定钧萍,這個回調(diào)就是父類ValueAnimator類實(shí)現(xiàn)的AnimationHandler類中的AnimationFrameCallback回調(diào),AnimationFrameCallback中的方法doAnimationFrame()在Choreographer類的FrameCallback回調(diào)中的方法doFrame()中被執(zhí)行脱货。
到這里的結(jié)論就是Choreographer通過調(diào)用doAnimationFrame()來驅(qū)動動畫執(zhí)行每一個關(guān)鍵幀。
-
startAnimation():
private void startAnimation() { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, getNameForTrace(), System.identityHashCode(this)); } mAnimationEndRequested = false; initAnimation(); mRunning = true; if (mSeekFraction >= 0) { mOverallFraction = mSeekFraction; } else { mOverallFraction = 0f; } if (mListeners != null) { notifyStartListeners(); } }
void initAnimation() { if (!mInitialized) { int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].init(); } mInitialized = true; } }
在前文中我們知道m(xù)Values其實(shí)就是PropertyValuesHolder律姨,也就是說 initAnimation的目的是
初始化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); } }
這里mEvaluator進(jìn)行了三目運(yùn)算振峻,由于前面我們執(zhí)行的是ObjectAnimator.ofFloat(),所以mEvaluator就是sFloatEvaluator择份,這里就涉及到了估值器:
private static final TypeEvaluator sFloatEvaluator = new 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); } }
init()方法的目的是初始化PropertyValuesHolder扣孟,初始化的時候,會確定具體的估值器荣赶,這個float類型的估值器只有一個evaluate()方法凤价,返回線性插入起始值和結(jié)束值的結(jié)果,就是根據(jù)時間的變化規(guī)律計算得到每一步的運(yùn)算結(jié)果拔创。關(guān)于估值器和插值器的相關(guān)文章
這里需要先說一下mKeyframes怎么來的呢利诺,正是前面提到的setIntValues方法中執(zhí)行的KeyframeSet.ofInt(values)。
public void setIntValues(int... values) { mValueType = int.class; mKeyframes = KeyframeSet.ofInt(values); }
-
setCurrentPlayTime():
public void setCurrentPlayTime(long playTime) { float fraction = mDuration > 0 ? (float) playTime / mDuration : 1; setCurrentFraction(fraction); }
public void setCurrentFraction(float fraction) { initAnimation(); fraction = clampFraction(fraction); mStartTimeCommitted = true; // do not allow start time to be compensated for jank if (isPulsingInternal()) { long seekTime = (long) (getScaledDuration() * fraction); 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); animateValue(currentIterationFraction); }
setCurrentFraction()方法基本都是時間的計算剩燥,最后執(zhí)行了animateValue()方法慢逾,這里先貼上ObjectAnimator類的該方法:
void animateValue(float fraction) { final Object target = getTarget(); ... super.animateValue(fraction); int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(target); } }
在ObjectAnimator類的animateValue()方法中,需要注意
-
super.animateValue(fraction);即執(zhí)行父類的animateValue(),
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); } } }
mInterpolator.getInterpolation(fraction);是獲取時間插值器侣滩,
mValues[i].calculateValue(fraction);是將時間插值送給估值器口注,計算出 values -
mValues[i].setAnimatedValue(target);
void setAnimatedValue(Object target) { if (mProperty != null) { mProperty.set(target, getAnimatedValue()); } if (mSetter != null) { try { mTmpValueArray[0] = getAnimatedValue(); mSetter.invoke(target, mTmpValueArray); } catch (InvocationTargetException e) { Log.e("PropertyValuesHolder", e.toString()); } catch (IllegalAccessException e) { Log.e("PropertyValuesHolder", e.toString()); } } }
注意,這個setAnimatedValue()方法是PropertyValuesHolder類的君珠,可以看到mSetter.invoke(target, mTmpValueArray)這行代碼通過反射進(jìn)行了屬性值的修改寝志。
也就是說setCurrentPlayTime()方法的目的1,是獲取時間插值器值和估值器策添,2材部,改變target的屬性
至此,動畫的第一幀就執(zhí)行完畢了舰攒。
我們通過ObjectAnimator.ofFloat()方法败富,查看了跟蹤查看了整個屬性動畫的機(jī)制。這里貼出動畫機(jī)制的相關(guān)方法摩窃,由于簡書的這個圖片壓縮的太狠兽叮,最后只有這張圖勉強(qiáng)能看清(建議右鍵,在新頁面打開圖片):
我們得出一些結(jié)論:
- 屬性動畫和我們生活中的動畫一樣猾愿,都是由一幀一幀構(gòu)成的鹦聪。
- 屬性動畫需要優(yōu)先計算出開始幀和結(jié)束幀。
- 屬性動畫通過start調(diào)用執(zhí)行動畫蒂秘,背后會進(jìn)行一系列的工作泽本。
- 屬性動畫依靠監(jiān)聽 Choreographer使得其可以不斷地調(diào)用 doAnimationFrame() 來驅(qū)動動畫執(zhí)行每一個關(guān)鍵幀。
- 每一次的 doAnimationFrame() 調(diào)用都會去計算時間插值姻僧,而通過時間插值器計算得到 fraction 又會傳給估值器规丽,使得估值器可以計算出屬性的當(dāng)前值。
- PropertyValuesHolder作為屬性動畫的變量封裝和管理和以及通過反射修改目標(biāo)屬性的值撇贺。
當(dāng)然赌莺,從簡單的角度來說動畫機(jī)制就是如此這般了,但是這只是粗顆粒而言松嘶,上文中海油很多細(xì)節(jié)并沒有展開艘狭,比如如何保證動畫唯一性、動畫的相關(guān)時間是如何計算的翠订、比如插值器和估值器是怎么工作的巢音、Choreographer又是如何不斷調(diào)用的等等問題,后續(xù)我會根據(jù)時間情況慢慢梳理出來尽超。