這篇文章的起因统舀,是因?yàn)橹霸趯W(xué)習(xí)屬性動(dòng)畫時(shí)更啄,自己查了很多資料奋蔚,也看了很多大佬關(guān)于屬性動(dòng)畫原理分析的文章送火,其中發(fā)現(xiàn)幾個(gè)問題:
- 很多優(yōu)秀的文章分析都是基于API版本25之前的源碼娘汞,而在API25之后的屬性動(dòng)畫的實(shí)現(xiàn)方式做了較大的改動(dòng)歹茶,其中最主要的部分就是AnimationHandler;
- 網(wǎng)絡(luò)上也有些關(guān)于API25之后源碼分析的文章,我個(gè)人覺得但大多數(shù)都不夠全面惊豺,也不夠深入;
基于這兩個(gè)原因我想自己分享一下自己在學(xué)習(xí)屬性動(dòng)畫時(shí)候的內(nèi)容尸昧,而且在寫的過程中也是一種鞏固和提升揩页。
先介紹動(dòng)畫庫中幾個(gè)核心類(以下內(nèi)容都是基于API28版本下)
- ValueAnimator:Animator的子類,實(shí)現(xiàn)了動(dòng)畫的整個(gè)處理邏輯烹俗,也是熟悉動(dòng)畫最為核心的類
- ObjectAnimator:對象屬性動(dòng)畫的操作類爆侣,繼承自ValueAnimator,通過該類使用動(dòng)畫的形式操作對象的屬性
- TimeInterpolator:時(shí)間插值器幢妄,它的作用是根據(jù)時(shí)間流逝的百分比來計(jì)算當(dāng)前屬性值改變的百分比兔仰,系統(tǒng)預(yù)置的有線性插值器、加速減速插值器蕉鸳、減速插值器等乎赴。
- TypeEvaluator:TypeEvaluator翻譯為類型估值算法,它的作用是根據(jù)當(dāng)前屬性改變的百分比來計(jì)算改變后的屬性值
- Property:屬性對象潮尝、主要定義了屬性的set和get方法
- PropertyValuesHolder:持有目標(biāo)屬性Property榕吼、setter和getter方法、以及關(guān)鍵幀集合的類勉失。
- KeyframeSet:存儲(chǔ)一個(gè)動(dòng)畫的關(guān)鍵幀集
按照慣例以通過具體的例子作為分析入口羹蚣,例子的內(nèi)容很簡單給view設(shè)置背景從紅色到藍(lán)色變化的動(dòng)畫,代碼如下:
public void startAnimator(View view){
ObjectAnimator animator = ObjectAnimator.ofInt(view, "backgroundColor", 0XFFF8080, 0xFF8080FF);
animator.setEvaluator(new ArgbEvaluator());//設(shè)置估值器
animator.setRepeatMode(ValueAnimator.REVERSE);//方向循環(huán)
animator.setRepeatCount(ValueAnimator.INFINITE);//無限重復(fù)
animator.setDuration(3000);
animator.start();
}
先從它的入口開始乱凿,即ObjectAnimator.ofInt入手:
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setIntValues(values);//設(shè)置屬性值
return anim;
}
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);//設(shè)置目標(biāo)
setPropertyName(propertyName);//設(shè)置目標(biāo)屬性名稱 也就是backgroundColor
}
在ObjectAnimato的ofInt函數(shù)中會(huì)構(gòu)建屬性動(dòng)畫對象顽素,并設(shè)置動(dòng)畫的目標(biāo)對象、目標(biāo)屬性名稱還有屬性值告匠。這個(gè)屬性values是個(gè)可變參數(shù)——如果是一個(gè)參數(shù)戈抄,那么該參數(shù)為目標(biāo)值;如果是兩個(gè)參數(shù)离唬,一個(gè)是起始值后专,另一個(gè)是目標(biāo)值。而如何設(shè)置屬性值的關(guān)鍵就在anim.setIntValues(values)函數(shù):
@Override
public void setIntValues(int... values) {
//這時(shí)候的mValues還是為空
if (mValues == null || mValues.length == 0) {
//mProperty也是為空输莺,我們并沒有用到Property
if (mProperty != null) {
setValues(PropertyValuesHolder.ofInt(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofInt(mPropertyName, values));//這里就是才是我們需要分析的入口
}
} else {
super.setIntValues(values);
}
}
在anim.setIntValues函數(shù)里出現(xiàn)了個(gè)核心類——PropertyValuesHolder戚哎。它的作用就是保存屬性名稱和該屬性的setter、getter方法嫂用,以及它的目標(biāo)值型凳。下面貼出該類在實(shí)例中分析所需要的相關(guān)代碼:
public class PropertyValuesHolder implements Cloneable {
//屬性名稱
String mPropertyName;
//屬性對象
protected Property mProperty;
//屬性的setter方法
Method mSetter = null;
//屬性的getter方法
private Method mGetter = null;
//屬性類型
Class mValueType;
//動(dòng)畫的關(guān)鍵幀,即動(dòng)畫在規(guī)定的時(shí)間內(nèi)動(dòng)畫幀的集合嘱函,它保存了每一幀該屬性對應(yīng)的值
Keyframes mKeyframes = null;
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
//1:構(gòu)建IntPropertyValuesHolder對象
return new IntPropertyValuesHolder(propertyName, values);
}
static class IntPropertyValuesHolder extends PropertyValuesHolder {
Keyframes.IntKeyframes mIntKeyframes;//IntKeyframeSet
//2:構(gòu)造函數(shù)
public IntPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);//設(shè)置屬性名稱
//設(shè)置屬性值
setIntValues(values);//設(shè)置屬性值
}
//3:設(shè)置動(dòng)畫的目標(biāo)值
@Override
public void setIntValues(int... values) {
super.setIntValues(values);//調(diào)用父類的setIntValues
mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
}
//計(jì)算當(dāng)前的動(dòng)畫值
@Override
void calculateValue(float fraction) {
mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);
}
}
}
PropertyValuesHolder算是一個(gè)輔助類甘畅,統(tǒng)一管理屬性名稱和屬性值。例如我們這里調(diào)用PropertyValuesHolder.ofInt()函數(shù),會(huì)生成正在的屬性類對象是IntPropertyValuesHolder疏唾,并給其設(shè)置了屬性名稱蓄氧、屬性值。而這里的關(guān)鍵其實(shí)是注釋3中的setIntValues函數(shù)槐脏,它是先調(diào)用其父類的setIntValues方法,再把mKeyframes設(shè)置給mIntKeyframes喉童。那我們就一起看看其父類的setIntValues函數(shù)做了什么:
public void setIntValues(int... values) {
mValueType = int.class;
//獲取到動(dòng)畫的關(guān)鍵幀
mKeyframes = KeyframeSet.ofInt(values);
}
這里又調(diào)用KeyframeSet的ofInt函數(shù),繼續(xù)跟蹤下去:
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {//設(shè)置1個(gè)目標(biāo)值
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
} else {//設(shè)置大于1個(gè)目標(biāo)值
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);//起始值
for (int i = 1; i < numKeyframes; ++i) {//遍歷單獨(dú)設(shè)置每個(gè)關(guān)鍵幀
keyframes[i] =
(IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
}
}
return new IntKeyframeSet(keyframes);
}
可以看到關(guān)鍵幀就是在ofInt函數(shù)中計(jì)算的顿天。首先values是個(gè)可變參數(shù)堂氯,如果設(shè)置的是一個(gè)值也就是numKeyframes==1,那么這個(gè)值就是最終值牌废,起始值默認(rèn)為0咽白。如果設(shè)置的目標(biāo)值個(gè)數(shù)大于1,按照比例設(shè)置各個(gè)關(guān)鍵幀的值:
這些關(guān)鍵幀都會(huì)存儲(chǔ)在IntKeyframeSet對象中鸟缕,再回到IntPropertyValuesHolder的setIntValues方法中mIntKeyframes屬性也設(shè)置好了值局扶。分析到這關(guān)鍵幀都設(shè)置完成了,我們需要回到ObjectAnimator的start方法叁扫,開始啟動(dòng)動(dòng)畫的分析:
@Override
public void start() {
AnimationHandler.getInstance().autoCancelBasedOn(this);
//代碼...
super.start();
}
調(diào)用父類的start方法也就是ValueAnimator的start方法:
private void start(boolean playBackwards) {
//判斷ui線程的looper是否是否為空
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
//無限循環(huán)次數(shù)的計(jì)算
mSeekFraction = 1 - fraction;
} else {
//有限循環(huán)次數(shù)的計(jì)算
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
/*步驟1:初始化值*/
//標(biāo)識動(dòng)畫是否已啟動(dòng)
mStarted = true;
//標(biāo)識動(dòng)畫是否處于暫停狀態(tài)
mPaused = false;
mRunning = false;
//跟蹤請求結(jié)束動(dòng)畫的標(biāo)志三妈。
mAnimationEndRequested = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
//步驟2:**關(guān)鍵點(diǎn)**
addAnimationCallback(0);
//步驟3:如果沒有延遲啟動(dòng),初始化動(dòng)畫設(shè)置一些監(jiān)聽,并設(shè)置time為0的當(dāng)前幀數(shù)的目標(biāo)值
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
startAnimation();
if (mSeekFraction == -1) {
//設(shè)置一些監(jiān)聽莫绣,并設(shè)置time為0的當(dāng)前幀數(shù)的目標(biāo)值
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
先總體歸納一下在start方法中做了幾件事情:
- 初始化動(dòng)畫的各種標(biāo)志位畴蒲;
- 最主要的的關(guān)鍵點(diǎn):注冊callBack回調(diào),獲取動(dòng)畫的下一幀回調(diào)对室;
- 如果沒有延遲啟動(dòng),初始化動(dòng)畫設(shè)置一些監(jiān)聽模燥,并設(shè)置time為0的當(dāng)前幀數(shù)的目標(biāo)值
然后我們單獨(dú)分析各個(gè)步驟,步驟一:沒什么特別的初始化了一些值掩宜,例如mStarted表示動(dòng)畫是否啟動(dòng)蔫骂、mPaused標(biāo)識動(dòng)畫是否處于暫停狀態(tài)、mReversing表示是否要reverse等牺汤。步驟二是最重要的一步先不分析辽旋,重要的事情永遠(yuǎn)留在最后。步驟三中有startAnimation和setCurrentPlayTime(0)兩個(gè)關(guān)鍵的方法檐迟。通常情況從命名上我們都以為startAnimation這是開啟動(dòng)畫的關(guān)鍵补胚。在開始看源碼的的時(shí)候我也這么認(rèn)為,可分析了半天發(fā)現(xiàn)其實(shí)不然追迟,但也做了很多關(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();//初始化動(dòng)畫
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
在startAnimation方法中最重要的一步就是initAnimation初始化動(dòng)畫,這里需要注意的一點(diǎn)是:這里調(diào)用的是ObjectAnimator.initAnimation敦间。我自己在分析的過程中瓶逃,就因?yàn)檫@個(gè)事情困了我好長時(shí)間束铭。因?yàn)樵谶@里的startAnimation()方法所屬在ValueAnimator類中,我們通過鼠標(biāo)點(diǎn)擊厢绝,必然直接進(jìn)入的是ValueAnimator的initAnimation函數(shù)纯露,而這里真正的調(diào)用對象是ObjectAnimator,所以我們需要的是調(diào)用對象的initAnimation函數(shù):
// class==>ObjectAnimator
void initAnimation() {
if (!mInitialized) {
// mValueType may change due to setter/getter setup; do this before calling super.init(),
// which uses mValueType to set up the default type evaluator.
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(target);//1:設(shè)置屬性的setter和getter方法
}
}
super.initAnimation();//2:調(diào)用父類的initAnimation函數(shù)
}
}
在上面注釋1中會(huì)調(diào)用PropertyValuesHolder的setupSetterAndGetter方法初始化屬性的setter和getter方法代芜,然后再調(diào)用ValueAnimator的initAnimation方法埠褪。
那我們先看一下如何設(shè)置setter和getter方法:
void setupSetterAndGetter(Object target) {
//省略 property為不為空的代碼
if (mProperty == null) {//property為空的情況下 在實(shí)例是我們需要分析的
Class targetClass = target.getClass();
if (mSetter == null) {
setupSetter(targetClass);//初始化屬性的setter 方法
}
List<Keyframe> keyframes = mKeyframes.getKeyframes();// 獲得前面設(shè)置的mKeyframes關(guān)鍵幀
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
if (!kf.hasValue() || kf.valueWasSetOnStart()) {
if (mGetter == null) {
setupGetter(targetClass);//初始化屬性的getter 方法
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
try {
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
kf.setValueWasSetOnStart(true);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
}
}
這里以分析setter方法為例,如果mSetter方法為空就通過setupSetter函數(shù)初始化目標(biāo)屬性的setter方法挤庇,繼續(xù)跟蹤下去:
@Override
void setupSetter(Class targetClass) {
if (mProperty != null) {
return;
}
// Check new static hashmap<propName, int> for setter method
synchronized(sJNISetterPropertyMap) {
HashMap<String, Long> propertyMap = sJNISetterPropertyMap.get(targetClass);//先從sJNISetterPropertyMap緩存中獲取
boolean wasInMap = false;
if (propertyMap != null) {
wasInMap = propertyMap.containsKey(mPropertyName);
if (wasInMap) {
Long jniSetter = propertyMap.get(mPropertyName);
if (jniSetter != null) {
mJniSetter = jniSetter;
}
}
}
//map如果緩存中沒有
if (!wasInMap) {
// 創(chuàng)建屬性set方法名
String methodName = getMethodName("set", mPropertyName);
try {
//調(diào)用JNI方法獲取 set方法是否存在
mJniSetter = nGetIntMethod(targetClass, methodName);//調(diào)用的是Native方法
} catch (NoSuchMethodError e) {
// Couldn't find it via JNI - try reflection next. Probably means the method
// doesn't exist, or the type is wrong. An error will be logged later if
// reflection fails as well.
}
//保存到緩存避免多次創(chuàng)建
if (propertyMap == null) {
propertyMap = new HashMap<String, Long>();
sJNISetterPropertyMap.put(targetClass, propertyMap);
}
propertyMap.put(mPropertyName, mJniSetter);
}
}
if (mJniSetter == 0) {
// Couldn't find method through fast JNI approach - just use reflection
super.setupSetter(targetClass);
}
}
static String getMethodName(String prefix, String propertyName) {
if (propertyName == null || propertyName.length() == 0) {
// shouldn't get here
return prefix;
}
//屬性名的第一個(gè)字母大寫
char firstLetter = Character.toUpperCase(propertyName.charAt(0));
//從第一個(gè)位置substring
String theRest = propertyName.substring(1);
//拼接屬性set方法 最后得到setBackground
return prefix + firstLetter + theRest;
}
//native 方法
native static private long nGetIntMethod(Class targetClass, String methodName);
以setter的創(chuàng)建過程為例:
- 先從sJNISetterPropertyMap緩存中根據(jù)目標(biāo)對象的classlei x獲取目標(biāo)對象的屬性propertyMap
- 如果propertyMap中有目標(biāo)屬性且屬性的set方法也存在钞速,就直接獲取jniSetter
- 如果不存在則通過調(diào)用getMethodName方法,拼接屬性的setter方法(set+屬性名第一個(gè)字母大寫+屬性名后面的字母)
- 通過調(diào)用native方法獲取 jniSetter
分析到這就設(shè)置完了屬性的setter和getter方法嫡秕,我們再次回到start()方法中渴语,這時(shí)候開始super.initAnimation()的調(diào)用:
// class==>ValueAnimator
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();//PropertyValuesHolder的init方法
}
mInitialized = true;//
}
}
PropertyValuesHolder的init初始化,并把mInitialized設(shè)置true:
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
//設(shè)置估值器
mKeyframes.setEvaluator(mEvaluator);
}
}
在init方法主要就是給mKeyframes關(guān)鍵幀設(shè)置估值器昆咽,如實(shí)例這種的ArgbEvaluator驾凶。
到這里ValueAnimator的startAnimation()方法都已經(jīng)全部分析完了,接下來就是分析setCurrentPlayTime(0)方法:
public void setCurrentPlayTime(long playTime) {
//playTime==0
float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;//fraction==0
setCurrentFraction(fraction);
}
public void setCurrentFraction(float fraction) {
initAnimation();
fraction = clampFraction(fraction);//fraction為0
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (isPulsingInternal()) {//是否啟動(dòng)動(dòng)畫循環(huán) 顯然不是
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);//把經(jīng)過的每一幀轉(zhuǎn)換為動(dòng)畫值
}
//
private float clampFraction(float fraction) {
if (fraction < 0) {
fraction = 0;
} else if (mRepeatCount != INFINITE) {//實(shí)例中設(shè)置的mRepeatCount為INFINITE
fraction = Math.min(fraction, mRepeatCount + 1);
}
return fraction;//最后結(jié)果還是0
}
這里涉及到各種計(jì)算掷酗,把fraction轉(zhuǎn)化為currentIterationFraction调违,我就不做詳細(xì)的分析來。fraction為0時(shí)泻轰,其實(shí)就是動(dòng)畫剛開始time為0技肩,算得的currentIterationFraction也為0。再調(diào)用animateValue函數(shù)把這時(shí)的0幀數(shù)轉(zhuǎn)化動(dòng)畫值也就是起始值浮声。同樣這里調(diào)用的也是ObjectAnimation的animateValue函數(shù):
//ObjectAnimation
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;
}
super.animateValue(fraction);//計(jì)算屬性值
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);//更新屬性值
}
}
在animateValue方法中先是調(diào)用了super.animateValue(fraction)方法計(jì)算屬性值虚婿,再調(diào)用PropertyValuesHolder的setAnimatedValue方法更新屬性值,所以我們還得要回到ValueAnimation的animateValue函數(shù)中:
//ValueAnimation
void animateValue(float fraction) {
//通過時(shí)間插值器——根據(jù)時(shí)間流逝的百分比來計(jì)算當(dāng)前屬性值改變的百分比泳挥,
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);// 計(jì)算當(dāng)前的動(dòng)畫值
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
分析到這終于看見TimeInterpolator時(shí)間插值器的身影,而時(shí)間插值器的作用是根據(jù)時(shí)間流逝的百分比來計(jì)算當(dāng)前屬性值改變的百分比然痊。獲取到當(dāng)前屬性的改變百分比后,在調(diào)用IntPropertyValuesHolder的calculateValue計(jì)算當(dāng)前的動(dòng)畫值
@Override
void calculateValue(float fraction) {
mIntAnimatedValue = mIntKeyframes.getIntValue(fraction);//IntKeyframeSet的getIntValue方法
}
IntKeyframeSet的getIntValue方法內(nèi)容:
@Override
public int getIntValue(float fraction) {
if (fraction <= 0f) {//1:當(dāng)前屬性改變的百分比小于等于0 也就是減小
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) {//2:屬性改變的百分比大于等于1
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();
}
//3:屬性改變的百分比小于1大于0
// mKeyframes存儲(chǔ)了關(guān)鍵幀
IntKeyframe prevKeyframe = (IntKeyframe) mKeyframes.get(0);//起始的幀數(shù)
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();
}
在getIntValue方法中屉符,計(jì)算當(dāng)前的動(dòng)畫值分為三種情況:
- 1:當(dāng)前屬性改變的百分比小于等于0, 也就是減芯缃;
- 2:屬性改變的百分比大于等于1筑煮;
- 3:屬性改變的百分比小于1大于0辛蚊;
我這里就以第三種情況為例粤蝎,分析它的處理過程真仲。首先是否還記得mKeyframes,在文章的前面我們分析關(guān)鍵幀時(shí)初澎,通過百分比的方式存放在mKeyframes中秸应。 在這里最開始獲取mKeyframes.get(0)為上一幀prevKeyframe虑凛,通過循環(huán)去下一幀,例如mKeyframes.get(1)為nextKeyframe软啼,通過 當(dāng)前幀數(shù)變化的百分比fraction與nextKeyframe中存放的百分比比較:
- 如果當(dāng)前的百分比下一幀的百分比桑谍,說面現(xiàn)在動(dòng)畫屬性值還在上一幀到下一幀的范圍內(nèi)。然后通過估值器mEvaluator計(jì)算出當(dāng)前的屬性值祸挪,如果估值器存在的話锣披,否則直接百分比的方式返回prevValue + (int)(intervalFraction * (nextValue - prevValue));
- 如果當(dāng)前的百分比大于一幀的百分比贿条, 那么上一幀prevKeyframe就等于下一幀nextKeyframe雹仿,然后循環(huán)去一下針比較;
計(jì)算完當(dāng)前動(dòng)畫屬性的值后整以,我們需要把屬性值設(shè)置到目標(biāo)對象上胧辽。這時(shí)候回到ObjectAnimation.animateValue()方法中,執(zhí)型IntPropertyValuesHolder.setAnimatedValue(target)函數(shù)設(shè)置屬性值:
@Override
void setAnimatedValue(Object target) {
if (mIntProperty != null) {
mIntProperty.setValue(target, mIntAnimatedValue);
return;
}
if (mProperty != null) {
mProperty.set(target, mIntAnimatedValue);
return;
}
if (mJniSetter != 0) {
nCallIntMethod(target, mJniSetter, mIntAnimatedValue);//
return;
}
if (mSetter != null) {
try {
mTmpValueArray[0] = mIntAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
native static private void nCallIntMethod(Object target, long methodID, int arg);
現(xiàn)在是不是已經(jīng)有點(diǎn)恍然大悟了公黑,在前面我們已經(jīng)分析了setter方法的初始化邑商,mJniSetter通過調(diào)用native方法獲得。這里再通過
native方法nCallIntMethod(Object target, long methodID, int arg)設(shè)置屬性值凡蚜。如果按照例子中的實(shí)際情況人断,其實(shí)是通過jni的方式調(diào)用了view的setBackgroundColor(color)方法,改變屬性值view開始重繪朝蜘。
到這里我們已經(jīng)設(shè)置了動(dòng)畫的起始值含鳞,離成就差一步了,喝口茶??芹务,休息一下蝉绷。
一盞茶的功夫繼續(xù)回來。我們的都知道動(dòng)畫簡單的說就是讓單個(gè)畫面有序的聯(lián)動(dòng)起來枣抱,在前面已經(jīng)給動(dòng)畫設(shè)置了起始值熔吗,想要畫面動(dòng)起來就得不停的一幀一幀的設(shè)置屬性值,那么怎么實(shí)現(xiàn)的呢佳晶?
我們需要回到開始分析的start()方法中的桅狠,之前我說——步驟二是最重要的一步先不分析,重要的事情永遠(yuǎn)留在最后轿秧,那現(xiàn)到了揭開這神秘面紗的時(shí)刻中跌,一探addAnimationCallback(0)方法的究竟:
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return
}
AnimationHandler handler = AnimationHandler.getInstance();
handler.addAnimationFrameCallback(this, delay);
}
Animation的addAnimationFrameCallback:
public class AnimationHandler {
private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime
=new ArrayMap<>();//存放延遲發(fā)送AnimationFrameCallback
private final ArrayList<AnimationFrameCallback> mAnimationCallbacks
=new ArrayList<>(); //存放發(fā)送的AnimationFrameCallback
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);//MyFrameCallbackProvider.postFrameCallback
}
if (!mAnimationCallbacks.contains(callback)) {. //
mAnimationCallbacks.add(callback);
}
if (delay > 0) {
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
}
}
//獲得MyFrameCallbackProvider對象
private AnimationFrameCallbackProvider getProvider() {
if (mProvider == null) {
mProvider = new MyFrameCallbackProvider();
}
return mProvider;
}
}
在AnimationHandler類有兩個(gè)集合,mDelayedCallbackStartTime用來存放延遲發(fā)送的AnimationFrameCallback菇篡,mAnimationCallbacks不管是否延遲都會(huì)存放在這漩符。關(guān)于mDelayedCallbackStartTime這處代碼的邏輯,有一點(diǎn)沒弄明白驱还。在我分析的API28版本的源碼中嗜暴,不管是否是延遲動(dòng)畫,因?yàn)樵谡{(diào)用addAnimationCallback(long delay)方法時(shí)凸克,delay永遠(yuǎn)都是0,也就是說延遲動(dòng)畫并沒有放在mDelayedCallbackStartTime這個(gè)延遲集合中闷沥∥剑可能是版本的原因,網(wǎng)上很多文章在分析延遲動(dòng)畫時(shí)舆逃,都是根據(jù)mDelayedCallbackStartTime中存放的callback和動(dòng)畫的延遲時(shí)間蚂维,來處理相關(guān)的邏輯,這個(gè)版本中延遲動(dòng)畫的處理并不是這樣路狮。
接著分析addAnimationFrameCallback方法鸟雏,如果mAnimationCallbacks還沒有callback就通過MyFrameCallbackProvider.postFrameCallback(callback)發(fā)送一個(gè)消息:
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
//Android系統(tǒng)的編舞者Choreographer
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
//代碼...
}
出現(xiàn)了一個(gè)新的內(nèi)容Choreographer,這是一個(gè)很重要也很復(fù)雜的內(nèi)容,一兩句沒法解釋清楚览祖。在這里我們只需要知道,系統(tǒng)會(huì)通過Choreographer的postFrameCallback方法最終會(huì)向底層注冊屏幕刷新信號的監(jiān)聽孝鹊,并回調(diào)FrameCallback的doFrame方法。
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());//處理動(dòng)畫的邏輯
if (mAnimationCallbacks.size() > 0) {//如果mAnimationCallbacks還有繼續(xù)向底層注冊監(jiān)聽
getProvider().postFrameCallback(this);
}
}
};
通俗點(diǎn)說展蒂,在Android中一般情況下1秒有60幀又活,1幀大概16ms。我們一幀一幀的繪制動(dòng)畫锰悼,代碼中最開始我們向底層注冊屏幕刷新的監(jiān)聽柳骄,當(dāng)接受到屏幕刷新的信號的時(shí)候,就會(huì)回調(diào)FrameCallback的doFrame箕般。然后在doFrame處理動(dòng)畫邏輯耐薯,如果動(dòng)畫沒有結(jié)束或者暫停,處理完這幀的動(dòng)畫邏輯丝里,繼續(xù)向底層注冊監(jiān)聽曲初,以此往復(fù)。
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;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);//在valueAnimator中實(shí)現(xiàn)了AnimationFrameCallback接口
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
上面代碼中在doFrame方法doAnimationFrame中杯聚,會(huì)調(diào)用callback.doAnimationFrame臼婆,而在ObjectAnimator中實(shí)現(xiàn)了AnimationFrameCallback接口:
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {//mStartTime初始值為-1
// First frame. If there is start delay, start delay count down will happen *after* this frame.
//如果設(shè)置了延遲開始 ,第一幀將在延遲之后開始
mStartTime = mReversing
? frameTime
: frameTime + (long) (mStartDelay * resolveDurationScale());
}
// Handle pause/resume
if (mPaused) {
mPauseTime = frameTime;
removeAnimationCallback();
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
}
}
if (!mRunning) {
if (mStartTime > frameTime && mSeekFraction == -1) {
//如果動(dòng)畫設(shè)置了延遲 直接返回
// This is when no seek fraction is set during start delay. If developers change the
// seek fraction during the delay, animation will start from the seeked position
// right away.
return false;
} else {
// If mRunning is not set by now, that means non-zero start delay,
// no seeking, not reversing. At this point, start delay has passed.
mRunning = true;
startAnimation();
}
}
if (mLastFrameTime < 0) {
if (mSeekFraction >= 0) {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
mLastFrameTime = frameTime;
boolean finished = animateBasedOnTime(currentTime);//處理動(dòng)畫關(guān)鍵幀
if (finished) {//如果動(dòng)畫完成
endAnimation();//結(jié)束動(dòng)畫移除 AnimationFrameCallback
}
return finished;
}
在這個(gè)方法中對延遲動(dòng)畫做了幌绍,如果設(shè)置了延遲颁褂,那么動(dòng)畫開始時(shí)間mStartTime將等于frameTime加上延遲時(shí)間,只有等待延遲時(shí)間已過才會(huì)處理后面的邏輯傀广,也就是往下執(zhí)行animateBasedOnTime:
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
final long scaledDuration = getScaledDuration();
//時(shí)間流逝的百分比
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
if (scaledDuration == 0) {
// 0 duration animator, ignore the repeat count and skip to the end
done = true;
} else if (newIteration && !lastIterationFinished) {
// Time to repeat
if (mListeners != null) {
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
mListeners.get(i).onAnimationRepeat(this);
}
}
} else if (lastIterationFinished) {
done = true;
}
//fraction 轉(zhuǎn)化為mOverallFraction
mOverallFraction = clampFraction(fraction);
//算得最后的時(shí)間流逝的百分比
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
// 關(guān)鍵點(diǎn)
animateValue(currentIterationFraction);
}
return done;
}
計(jì)算時(shí)間的流逝占總動(dòng)畫的百分比颁独,再調(diào)用ObjectAnimator的animateValue方法:
//ObjectAnimation
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;
}
super.animateValue(fraction);//計(jì)算屬性值
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);//更新屬性值
}
}
在前面動(dòng)畫剛開始的時(shí)候已經(jīng)分析過這個(gè)方法,先計(jì)算當(dāng)前時(shí)段的屬性值然后在更新屬性的值伪冰,就這么在動(dòng)畫的時(shí)間內(nèi)范圍內(nèi)一幀一幀的設(shè)置更新屬性誓酒,畫面就動(dòng)起來了,終于大功告成糜值。
····