Android深入分析屬性動畫源碼
1.先看一段動畫的代碼實現(xiàn)
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1); alpha.setDuration(500); alpha.start();
代碼很簡單,上面三行代碼就可以開啟一個透明度變化的動畫。 那么android系統(tǒng)到底是如何實現(xiàn)的呢?進入源碼分析。
1)看第一行代碼:
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1, 0,1);
創(chuàng)建了一個ObjectAnimator對象,并把values數(shù)組設(shè)置給了anim對象。
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}ObjectAnimator 構(gòu)造函數(shù)中。將傳過來的View對象和propertyName賦值給成員變量。
private ObjectAnimator(Object target, String propertyName) {
//將傳過來的View對象賦值給成員變量mTarget
setTarget(target);
//將propertyName賦值給成員變量mPropertyName
setPropertyName(propertyName);
}注意這個mTarget為什么要用一個軟引用?
那是為了防止Activity發(fā)生內(nèi)存泄漏。因為會有Activity已經(jīng)退出,但是動畫可能還未執(zhí)行完,這個時候View得不到釋放的話,會引發(fā)Activity內(nèi)存泄漏。
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,所以會執(zhí)行這行代碼。 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數(shù)組賦值給成員變量PropertyValuesHolder[] mValues;
再看PropertyValuesHolder.ofFloat(mPropertyName, values));
先調(diào)用super構(gòu)造函數(shù),將propertyName賦值給父類的mPropertyName,
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}然后再調(diào)用setFloatValues(values);
public void setFloatValues(float... values) {
super.setFloatValues(values);
//將mKeyframes強轉(zhuǎn)為mFloatKeyframes
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
//調(diào)用父類方法創(chuàng)建了KeyframeSet對象,賦值給了mKeyframes
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}KeyframeSet.ofFloat(values);這行代碼創(chuàng)建了一個關(guān)鍵幀的集合。
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
//創(chuàng)建一個value長度的 FloatKeyFrame的數(shù)組
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 {
//下面的代碼才是關(guān)鍵的 Keyframe ofFloat(float fraction, float value)是創(chuàng)建關(guān)鍵幀。
//fraction英文單詞意思是部分,在這作為參數(shù)的意思是:從動畫啟示位置,到當(dāng)前位置,所占的整個動畫的百分比。
//value就是某個部分對應(yīng)的屬性值。
// 比如傳進來的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)。而這個位置對應(yīng)的動畫的屬性值,就是value[i]
//所以這個keyframes[]數(shù)組的目的就是保存,動畫的關(guān)鍵位置所占的百分比和關(guān)鍵位置對應(yīng)的屬性值。
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);
}到這為止,第一行代碼執(zhí)行完畢。
ObjectAnimator.ofFloat(view, "alpha", 1, 0,1)
將view賦值給ObjectAnimator成員變量。
將propertyName賦值給PropertyValuesHolder,會通過屬性name來反射它的set方法,用來修改屬性值。
創(chuàng)建KeyframeSet,關(guān)鍵幀集合。將value數(shù)組轉(zhuǎn)換成對應(yīng)的關(guān)鍵幀集合,通過動畫執(zhí)行的時間,來計算當(dāng)前時間對應(yīng)的屬性值,然后再調(diào)用view的set屬性方法,從而達到形成動畫的目的。
這塊的代碼會再后面看到。
2).看動畫的第二行代碼alpha.start();
ObjectAnimator的父類是ValueAnimator。start()里面調(diào)用到的方法會在子類和父類里跳來跳去,這也增大了閱讀的難度。
首先看ValueAnimator#start(boolean playBackwards)方法
addAnimationCallback:向Choreographer注冊回調(diào)函數(shù),我們知道Choreographer可以接受Vsync信號,16.66ms一次,也是屏幕刷新一次的時間。這樣在屏幕刷新的時候,就可以通過向Choreographer注冊回調(diào)函數(shù)進行動畫的更新。
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;
//這個是一個回調(diào)函數(shù)。這塊是由Choreographer回調(diào)的,稍后分析。
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
//開始動畫。
startAnimation();
}
}先看startAnimation方法(),會在這個方法中調(diào)用initAnimation();
在這會先調(diào)用子類ObjectAnimator,然后在調(diào)用父類的ValueAnimator的initAnimation方法。
先看子類的initAnimation(),這個方法根據(jù)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中傳進了屬性值,下面這行代碼就是根據(jù)屬性值,來反射view的set方法,
//通過set方法,就可以動態(tài)的改變view的屬性值的變化。
mValues[i].setupSetterAndGetter(target);
}
}
//調(diào)用父類的initAnimation()方法
super.initAnimation();
}
}再看父類ValueAnimator的initAnimation方法。調(diào)用了PropertyValuesHolder的init()方法。
在init方法中,向KeyframeSet關(guān)鍵幀集合設(shè)置了一個估值器,這個用來計算屬性值的,后面會看到具體的計算方法。
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//調(diào)用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中設(shè)置一個估值器,這個估值器用來計算動畫在某個時刻的屬性值。
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
這個方法返回一個在動畫開始和結(jié)束之間的一個線性的結(jié)果。其實就是個一元一次方程,來計算動畫當(dāng)前的位置。
//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的代碼已經(jīng)執(zhí)行完畢。主要做的工作可以總結(jié)為兩點:
1.調(diào)用PropertyValuesHolder的setupSetterAndGetter方法,通過反射拿到View的setter方法。
2.向KeyframeSet中設(shè)置一個估值器,用來計算動畫某一時刻的屬性值。
3)接下來看ValueAnimator#addAnimationCallback
這個方法是向Choreographer設(shè)置了一個會回調(diào)函數(shù),每隔16.66ms回調(diào)一次,用來刷新動畫。
還設(shè)置了一個回調(diào)集合,在Choreographer的回調(diào)函數(shù)中,回調(diào)集合里面的回調(diào)函數(shù),來實現(xiàn)屬性動畫的刷新
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
//getAnimationHandler 就是上面創(chuàng)建的AnimationHandler。
//將this作為 AnimationFrameCallback的回調(diào),會回調(diào)doAnimationFrame(long frameTime)
getAnimationHandler().addAnimationFrameCallback(this, delay);
}//AnimationHandler#addAnimationFrameCallback
getProvider()拿到的是MyFrameCallbackProvider。
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
//向Choreographer加入一個回調(diào)函數(shù)mFrameCallback
getProvider().postFrameCallback(mFrameCallback);
}
//將添加的回調(diào)函數(shù)加入一個回調(diào)的集合。
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);
}
}先看這個getProvider().postFrameCallback(mFrameCallback);這個就是向Choreographer注冊一個回調(diào)。
final Choreographer mChoreographer = Choreographer.getInstance();
//這行代碼是向編舞者Choreographer添加了一個回調(diào)函數(shù)。
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的回調(diào)函數(shù)。 callback 就是傳進來的mFrameCallback。
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}省略中間的調(diào)用過程。。。這塊的代碼在Choreographer源碼分析過。
MyFrameCallbackProvider#postFrameCallback就是向Choreographer添加一個回調(diào)函數(shù)。 我們知道,Choreographer在接收到Vsync信號后調(diào)用這些回調(diào)函數(shù)。
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);
}最終會調(diào)到這里,根據(jù)上面?zhèn)鬟^來的token,轉(zhuǎn)換成不同的回調(diào)函數(shù),調(diào)用不同的方法。
//在將View繪制時,調(diào)用的是else分支的回調(diào)
//在動畫這里,傳進來的是mFrameCallback,Choreographer.FrameCallback的實例,會調(diào)用到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注冊回調(diào),等到下一次Vsync信號來的時候調(diào)用,
//針對于60Hz的屏幕,刷新時間間隔是16.66ms,也就是Vsync回調(diào)的時間間隔
//也就是說屬性動畫16.66毫秒會改變一次
getProvider().postFrameCallback(this);
}
}
};Choreographer中每個16.6ms會回調(diào)doFrame方法(),在doAnimationFrame方法中,就會回調(diào)注冊的回調(diào)集合。
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,調(diào)用callBack回調(diào)函數(shù),
//這個回調(diào)函數(shù)是ValueAnimator的doAnimationFrame
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
}
}
}doAnimationFrame是AnimationFrameCallback的回調(diào)函數(shù),由ValueAnimator實現(xiàn)。
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();
//通過計算得到動畫當(dāng)前執(zhí)行占比多少。(currentTime - mStartTime)動畫執(zhí)行的時間
//除以scaledDuration總時間,得到就是已經(jīng)執(zhí)行的部分,如果是一個重復(fù)的動畫,這個值可能會大于1.
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
//下面通過計算對fraction進行修正,減去重復(fù)執(zhí)行的部分,得到真正的在一次動畫中要執(zhí)行到哪一部分
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
return done;
}注意animateValue,這個方法在父類ValueAnimator和子類ObjectAnimator都有實現(xiàn)。
所以這里先調(diào)用子類ObjectAnimator的方法。
//這個方法是調(diào)用的子類的方法
void animateValue(float fraction) {
final Object target = getTarget();
if (mTarget != null && target == null) {
cancel();
return;
}
//先調(diào)用父類的方法
super.animateValue(fraction);
//再回到子類
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//給View設(shè)置改變后的屬性值
mValues[i].setAnimatedValue(target);
}
}先看super.animateValue方法,這個方法就是去計算動畫變動后的屬性值。
void animateValue(float fraction) {
//通過插值器,來修改。如果沒有設(shè)置插值器,那么fraction的變化就是勻速的。
//經(jīng)過插值器的計算,fraction的變化就會呈現(xiàn)出加速、減速變化的效果。
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
//PropertyValuesHolder[] mValues,因為一個View可以有多個屬性動畫,所以這用一個數(shù)組來存儲。
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 就是前面創(chuàng)建的關(guān)鍵幀集合KeyframeSet
Object value = mKeyframes.getValue(fraction);
// 將得到的值,賦值給mAnimatedValue
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}下面這個方法是真正去計算改變后的屬性值。通過估值器mEvaluator去計算的。
public Object getValue(float fraction) {
//第一關(guān)鍵幀記做前一關(guān)鍵幀
Keyframe prevKeyframe = mFirstKeyframe;
for (int i = 1; i < mNumKeyframes; ++i) {
//得到下一關(guān)鍵幀
Keyframe nextKeyframe = mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
//得到前一關(guān)鍵幀,對應(yīng)的部分
final float prevFraction = prevKeyframe.getFraction();
//fraction - prevFraction 當(dāng)前要執(zhí)行的部分距離前一關(guān)鍵幀是多少。
//nextKeyframe.getFraction() - prevFraction,這一幀有多少
//兩者相除,得到的就是當(dāng)前部分在這一幀的占比
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
//不應(yīng)該執(zhí)行到這里,在上面的for循環(huán)就應(yīng)該返回當(dāng)前動畫,屬性變化的大小。
return mLastKeyframe.getValue();
}通過估值器計算view的屬性值。
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
//通過一個一元一次方程,來計算得到當(dāng)前的屬性值。
return startFloat + fraction * (endValue.floatValue() - startFloat);
}至此,動畫要變動后的屬性值,已經(jīng)計算出來了,
通過 mValues[i].setAnimatedValue(target);用來修改View的屬性值大小。
void setAnimatedValue(Object target) {
//前面已經(jīng)通過反射拿到了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屬性動畫的整個執(zhí)行流程已經(jīng)分析完畢。
可以總結(jié)以下幾點:
1.ValueAnimator是父類,ObjectAnimator是子類,這里面封裝了一個target,也就是view對象。
2.PropertyValuesHolder,有屬性名,屬性值,通過屬名來反射view的setter方法,來動態(tài)修改屬性值。
3.KeyframeSet,是一個關(guān)鍵幀集合,封裝了定義動畫是value數(shù)組的值,每一個值都被記錄為一個關(guān)鍵幀F(xiàn)loatKeyframe。
4.通過插值器,可以改變屬性變化的快慢,通過估值器計算屬性值的大小。
5.給Choreographer注冊了一個回調(diào),每隔16.66ms回調(diào)一次,每一次回調(diào)都會去改變view屬性值的大小。改變是通過fraction計算的,進而通過計算得到改變后的屬性值大小。
這樣動態(tài)的改變view屬性值的大小,就連貫的形成一幅動畫。
到此這篇關(guān)于Android深入分析屬性動畫源碼的文章就介紹到這了,更多相關(guān)Android屬性動畫內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android CountDownTimer實現(xiàn)倒計時器
這篇文章主要為大家詳細介紹了Android CountDownTimer實現(xiàn)倒計時效果的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-02-02
android studio xml文件實現(xiàn)添加注釋
這篇文章主要介紹了android studio xml文件實現(xiàn)添加注釋,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
android Jsoup獲取網(wǎng)站內(nèi)容 android獲取新聞標題實例
這篇文章主要為大家詳細介紹了android Jsoup獲取網(wǎng)站內(nèi)容,android獲取新聞標題實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-03-03
android中webview控件和javascript交互實例
這篇文章主要介紹了android中webview控件和javascript交互實例,例子中包括javascript調(diào)用java的方法,java代碼中調(diào)用javascript的方法,需要的朋友可以參考下2014-07-07
初學(xué)Android之網(wǎng)絡(luò)封裝實例
大家好,本篇文章主要講的是初學(xué)Android之網(wǎng)絡(luò)封裝實例,感興趣的同學(xué)趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
Android開發(fā)自學(xué)筆記(四):APP布局下
這篇文章主要介紹了Android開發(fā)自學(xué)筆記(四):APP布局下,本文是上一篇的補充,需要的朋友可以參考下2015-04-04

