編輯:關於Android編程
Android中想做很炫酷的動畫效果,相信在很多時候你都可以選擇使用屬性動畫,關於屬性動畫如何使用,我們已經很詳細的寫過兩篇博客講解。如果你還不了解,請參考:
Android 屬性動畫(Property Animation) 完全解析 (上)
Android 屬性動畫(Property Animation) 完全解析 (下)
本篇博客將分析屬性動畫的實現源碼,帶你深入的了解Android屬性動畫的內部實現機制。如果你經常用屬性動畫,但又一直沒有去查看其源碼實現,沒關系,請往下看。
在源碼分析之前,我們需要有一個明確的思路,例如:源碼的入口的選擇、甚至對其實現進行簡單的猜測,源碼分析相當於一個驗證的過程,帶著一個目標去看源碼,這樣的話,分析和理解起來更為方便。
對於實現屬性動畫,最常用的類就是ObjectAnimator了,只需要簡單的設置目標view,屬性,以及目標值等必要屬性,調用一下start();我們的動畫就完成了。
類似如下代碼:
ObjectAnimator .ofInt(target,propName,values[]) .setInterpolator(LinearInterpolator) .setEvaluator(IntEvaluator) .setDuration(500) .start();
然後是設置插值器,當然了插值器這個詞比較難理解,我要是說例如:AccelerateInterpolator、LinearInterpolator
然後設置估值算法,這個看名字挺高端,其實內部實現尤其簡單: return (int)(startInt + fraction * (endValue - startInt)); 開始值,加上當前的屬性改變的百分比*(結束-開始)
當然了,這個百分比是fraction ,其實就是上面的插值器算出來的。比如線性插值器:fraction 值就是currentTime - mStartTime) / mDuration,動畫的運行時間/總設置時間。
然後是設置動畫事件,
最後start()。
好了,現在我想問個問題,根據上面這些參數,如果我要你設計個屬性動畫框架,你怎麼做?
這個嘛,好整,拿到上述參數之後,start()中,開啟一個定時器,去執行一個任務;在任務內部,根據Interpolator計算出來的fraction,交給Evaluator,得到屬性當前應該設置的值,然後反射設置tagert的指定屬性,ok,奏事這麼簡單。嗯,大體上應該就是這樣,當然了,源碼的實現肯定復雜很多,但是萬變不離其宗,所以接下來的源碼閱讀,就是去驗證我們的這個答案。
好了,猜想完了,我們就得進入驗證階段了~~
那麼,我們源碼的入口就是上述代碼了,不過貌似上述代碼調用了好幾個方法,but,我覺得start之前的代碼,無法是初始化實例,設置一些成員變量。
首先我們看ofInt,這裡為了簡單,我們的ofInt中的values參數,默認就一個,類似 .ofInt(view, translationX, 300) ;
public static ObjectAnimator ofInt(Object target, String propertyName, int... values) { ObjectAnimator anim = new ObjectAnimator(target, propertyName); anim.setIntValues(values); return anim; }
首先調用ObjectAnimator的構造方法傳入了一個target和propName,估計就是創建對象,然後舊路下target和propName,簡單看下
private ObjectAnimator(Object target, String propertyName) { mTarget = target; setPropertyName(propertyName); } public void setPropertyName(String propertyName) { //... mPropertyName = propertyName; mInitialized = false; }
@Override public void setIntValues(int... values) { setValues(PropertyValuesHolder.ofInt(mPropertyName, values)); }
從字面上看,是保存view在動畫期間的屬性和值,記住是動畫期間的。繼續往下看:
public static PropertyValuesHolder ofInt(String propertyName, int... values) { return new IntPropertyValuesHolder(propertyName, values); } public IntPropertyValuesHolder(String propertyName, int... values) { mPropertyName = propertyName; setIntValues(values); } @Override public void setIntValues(int... values) { mValueType = int.class; mKeyframeSet = KeyframeSet.ofInt(values); mIntKeyframeSet = (IntKeyframeSet) mKeyframeSet; }
這裡又出現一個新名詞,叫做mKeyframeSet,這個是由 KeyframeSet.ofInt(values);得到的。
那麼這個KeyframeSet是什麼呢?單純的理解是,Keyframe的集合,而Keyframe叫做關鍵幀,為一個動畫保存time/value(時間與值)對。
那麼我們去看看它是如何通過KeyframeSet.ofInt(values);去構造與保存的:
public static KeyframeSet ofInt(int... values) { int numKeyframes = values.length; IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)]; if (numKeyframes == 1) { keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f); keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]); } else { //... } return new IntKeyframeSet(keyframes); } public IntKeyframeSet(IntKeyframe... keyframes) { mNumKeyframes = keyframes.length; mKeyframes = new ArrayList(); mKeyframes.addAll(Arrays.asList(keyframes)); mFirstKeyframe = mKeyframes.get(0); mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); mInterpolator = mLastKeyframe.getInterpolator(); }
根據我們的values的長度,構造了keyframes數組,然後分別通過Keyframe的ofInt方法,去構造keyframe對象,其實在內部:
IntKeyframe(float fraction, int value) { mFraction = fraction; mValue = value; mValueType = int.class; mHasValue = true; } IntKeyframe(float fraction) { mFraction = fraction; mValueType = int.class; }
拿到初始化完成的keyframes數組以後,將其傳入了KeyframeSet的構造方法,初始化了KeyframeSet內部的一些成員變量。
public IntKeyframeSet(IntKeyframe... keyframes) { mNumKeyframes = keyframes.length; mKeyframes = new ArrayList(); mKeyframes.addAll(Arrays.asList(keyframes)); mFirstKeyframe = mKeyframes.get(0); mLastKeyframe = mKeyframes.get(mNumKeyframes - 1); mInterpolator = mLastKeyframe.getInterpolator(); }
到此,我們的(PropertyValuesHolder.ofInt在徹底返回,可以看到這個過程中,我們成功的為PropertyValuesHolder對象賦值了propName,valueType,keyframeSet .
keyframeset中存了Keyframe集合,keyframe中存儲了(fraction , valuetype , value , hasValue)。
最後,叫 PropertyValuesHolder 交給我們的 ObjectAnimator的setValues方法。
public void setValues(PropertyValuesHolder... values) { int numValues = values.length; mValues = values; mValuesMap = new HashMap首先記錄了mValues,注意這裡的values是PropertyValuesHolder類型的,然後通過一個mValueMap記錄:key為屬性的名稱,值為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; }
好了,到此我們的ofInt結束了,暈否,其實還好。如果你暈了,我幫你總結下:ofInt就是記錄了target,propName,values(是將我們傳入的int型values,輾轉轉化成了PropertyValuesHolder),以及一個mValueMap,這個map的key是propName,value是PropertyValuesHolder,在PropertyValuesHolder內部又存儲了proprName, valueType , keyframeSet等等。
好了,接下來會輕松點,按照順序到setInterpolator了:
@Override public void setInterpolator(TimeInterpolator value) { if (value != null) { mInterpolator = value; } else { mInterpolator = new LinearInterpolator(); } }
然後是setEvaluator。
public void setEvaluator(TypeEvaluator value) { if (value != null && mValues != null && mValues.length > 0) { mValues[0].setEvaluator(value); } }
public void setEvaluator(TypeEvaluator evaluator) { mEvaluator = evaluator; mKeyframeSet.setEvaluator(evaluator); }
public void setEvaluator(TypeEvaluator evaluator) { mEvaluator = evaluator; }
接下來,最後一個屬性,duration
// How long the animation should last in ms private long mDuration = (long)(300 * sDurationScale); private long mUnscaledDuration = 300; private static float sDurationScale = 1.0f; public ObjectAnimator setDuration(long duration) { if (duration < 0) { throw new IllegalArgumentException(Animators cannot have negative duration: + duration); } mUnscaledDuration = duration; mDuration = (long)(duration * sDurationScale); return this; }
好了,到此該設置的設置完成了,小小總結一下:
ofInt中實例化了一個ObjectAnimator對象,然後設置了target,propName,values(PropertyValuesHolder) ;然後分別在setInterpolator,setDuration設置了Interpolator和duration。其中setEvaluator是給values[0],以及keyframeSet設置估值算法。
PropertyValueHolder實際上是IntPropertyValueHolder類型對象,包含propName,valueType,keyframeSet .
keyframeset中存了Keyframe集合,keyframe中存儲了(fraction , valuetype , value , hasValue)。
以上都比較簡單,關鍵就是看start()方法中,如何將這些屬性進行合理的處理調用神馬的。
喝杯水,小憩一下,准備征戰start()方法。
@Override public void start() { super.start(); } ValueAnimator @Override public void start() { start(false); } ValueAnimator private void start(boolean playBackwards) { if (Looper.myLooper() == null) { throw new AndroidRuntimeException(Animators may only be run on Looper threads); } mPlayingBackwards = playBackwards; mCurrentIteration = 0; mPlayingState = STOPPED; mStarted = true; mStartedDelay = false; mPaused = false; AnimationHandler animationHandler = getOrCreateAnimationHandler(); animationHandler.mPendingAnimations.add(this); if (mStartDelay == 0) { // This sets the initial value of the animation, prior to actually starting it running setCurrentPlayTime(0); mPlayingState = STOPPED; mRunning = true; notifyStartListeners(); } animationHandler.start(); }最終調用了ValueAnimator的statr(playBackwards)方法;
15-20行:設置了關於動畫的一些標志位,mPlayingBackwards 表示動畫是否reverse;mCurrentIteration 記錄當前的動畫的執行次數(與setRepeatCount有關);mPlayingState 動畫的狀態為STOPPED;還有些其他的標志位;
21行:生成一個AnimationHandler對象,getOrCreateAnimationHandler就是在當前線程變量ThreadLocal中取出來,沒有的話,則創建一個,然後set進去。
AnimationHandler中包含一些List集合用於存儲各種狀態的ValueAnimator。
22行:將當前ValueAnimator對象,加入 animationHandler.mPendingAnimations 集合。
23行:未設置mStartDelay,默認為0,則進入循環;
24行: setCurrentPlayTime(0);一會需要細說
25-26行:設置些狀態。
27行:回調監聽動畫的接口AnimatorListener的onAnimationStart方法,如果你設置了回調監聽,此時就會進行回調;
最後30行:調用animationHandler.start();需要細說;
好了,有兩個方法需要細說,首先看setCurrentPlayTime(0)
public void setCurrentPlayTime(long playTime) { initAnimation(); long currentTime = AnimationUtils.currentAnimationTimeMillis(); if (mPlayingState != RUNNING) { mSeekTime = playTime; mPlayingState = SEEKED; } mStartTime = currentTime - playTime; doAnimationFrame(currentTime); }
首先初始化動畫,然後得到當前的系統開始到現在的時間currentTime;設置mSeekTime,設置當前狀態為SEEKED;然後使用mSeekTime-playTime得到動畫現在需要執行的時間;最後調用 doAnimationFrame(currentTime),稍後看其代碼;
關於initAnimation(),實際就是去設置我們ValueAnimator中存儲的mValues,也就是IntPropertyValueHolder的mEvaluator;
void initAnimation() { if (!mInitialized) { int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].init(); } mInitialized = true; }
PropertyValuesHolder的init方法:
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 mKeyframeSet.setEvaluator(mEvaluator); } }其實就是遍歷設置PropertyValuesHolder中的mEvaluator屬性,默認根據valueType進行判斷,IntEvaluator或者FloatEvaluator。
接下來應該看doAnimationFrame(currentTime);了
final boolean doAnimationFrame(long frameTime) { final long currentTime = Math.max(frameTime, mStartTime); return animationFrame(currentTime); }
boolean animationFrame(long currentTime) { boolean done = false; switch (mPlayingState) { case RUNNING: case SEEKED: float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f; if (fraction >= 1f) { //... } if (mPlayingBackwards) { fraction = 1f - fraction; } animateValue(fraction); break; } return done; }
接下來調用了animateValue(fraction)
在animateValue的內部,會將傳入的fraction,交給 mInterpolator.getInterpolation(fraction);方法,獲得插值器處理後的fraction;然後在將fraction交給估值算法mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();進行計算得到當前時間點,屬性應該的值;最後會反射對我們設置的屬性進行設置。
終於看到,對我們的屬性的值進行設置了,偶也~~當然了,動畫如果沒結束,應該每隔一定的幀數,再次調用,嗯,的確是這樣的,你看到animationFrame最後是不是有個返回值,這個值會在fraction>=1的時候返回true;
我們還是先看看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); } } int numValues = mValues.length; for (int i = 0; i < numValues; ++i) { mValues[i].setAnimatedValue(mTarget); } }
首先將fraction交給給 mInterpolator.getInterpolation(fraction);得到計算後的fraction;
然後for循環遍歷調用IntPropertyValueHolder的calculateValue方法:
void calculateValue(float fraction) { mAnimatedValue = mKeyframeSet.getValue(fraction); }
@Override public Object getValue(float fraction) { return getIntValue(fraction); } public int getIntValue(float fraction) { if (mNumKeyframes == 2) { if (firstTime) { firstTime = false; firstValue = ((IntKeyframe) mKeyframes.get(0)).getIntValue(); lastValue = ((IntKeyframe) mKeyframes.get(1)).getIntValue(); deltaValue = lastValue - firstValue; } if (mInterpolator != null) { fraction = mInterpolator.getInterpolation(fraction); } if (mEvaluator == null) { return firstValue + (int)(fraction * deltaValue); } else { return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue(); } } //...省略了很多代碼 }
然後16-20行,調用估值算法的mEvaluator.evaluate方法,可以看到如果mEvaluator == null直接調用了firstValue + (int)(fraction * deltaValue);其實這個就是IntEvaluator的默認實現。
好了,for循環結束了,經過我們插值器和估值算法得出的值,最終給了IntPropertyValueHolder的mIntAnimatedValue屬性;
回到animateValue方法:在animateValue的8-12行,繼續回調動畫監聽onAnimationUpdate(this);方法;
animateValue的15-18行:循環拿到(其實我們就只有一個屬性)我們的IntPropertyValueHolder調用setAnimatedValue,進行反射為我們的屬性設置值,反射需要一些東西,比如target,propname,以及該屬性應該設置的值;這三個參數在哪呢?target作為參數傳入了,propName初始化的時候就設置了,至於該屬性應該設置的值,上面有一句:“ 好了,for循環結束了,經過我們插值器和估值算法得出的值,最終給了IntPropertyValueHolder的mIntAnimatedValue屬性 ” 。是不是全了~~反射的代碼就不貼了。
好了,到此,我們屬性動畫,設置的各種值,經過重重的計算作用到了我們的屬性上,反射修改了我們的屬性。到此我們已經完成了一大半,但是貌似還少了個,每隔多少幀調用一次~~
嗯,的確是的,跨度好大,現在回到我們的start方法,最後一行:調用animationHandler.start();這個還沒細說呢~~
animationHandler我們上面已經介紹了,存儲在當前線程的ThreadLocal裡面,裡面放了一些集合用於存儲各種狀態的ObjectAnimator,我們當前的ObjectAnimator對象也存儲在其mPendingAnimations的集合中(上面提到過~~)。
/** * Start animating on the next frame. */ public void start() { scheduleAnimation(); } private void scheduleAnimation() { if (!mAnimationScheduled) { mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null); mAnimationScheduled = true; } }
說這麼多,其實就是一句話,這裡調用了animationHandler的 run方法。
public void run() { mAnimationScheduled = false; doAnimationFrame(mChoreographer.getFrameTime()); } private void doAnimationFrame(long frameTime) { while (mPendingAnimations.size() > 0) { ArrayListpendingCopy = (ArrayList ) mPendingAnimations.clone(); mPendingAnimations.clear(); int count = pendingCopy.size(); for (int i = 0; i < count; ++i) { ValueAnimator anim = pendingCopy.get(i); // If the animation has a startDelay, place it on the delayed list if (anim.mStartDelay == 0) { anim.startAnimation(this); } else { mDelayedAnims.add(anim); } } } //...省略了一些代碼 // Now process all active animations. The return value from animationFrame() // tells the handler whether it should now be ended int numAnims = mAnimations.size(); for (int i = 0; i < numAnims; ++i) { mTmpAnimations.add(mAnimations.get(i)); } for (int i = 0; i < numAnims; ++i) { ValueAnimator anim = mTmpAnimations.get(i); if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) { mEndingAnims.add(anim); } } mTmpAnimations.clear(); if (mEndingAnims.size() > 0) { for (int i = 0; i < mEndingAnims.size(); ++i) { mEndingAnims.get(i).endAnimation(this); } mEndingAnims.clear(); } // If there are still active or delayed animations, schedule a future call to // onAnimate to process the next frame of the animations. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { scheduleAnimation(); } }
在anim.startAnimation(this);內部其實主要就一行代碼:handler.mAnimations.add(this); 將當前動畫加入animationHandler的mAnimations集合;
26-29行:將animationHandler的mAnimations集合中的每個anim,加入到mTmpAnimations中;
30-35行:依次調用mTmpAnimations中的anim,anim.doAnimationFrame(frameTime)
doAnimationFrame(frameTime)上面已經分析過了,如果返回true,即doAnimationFrame的done為true,則將該動畫加入到結束動畫集合。
37-43行:循環調用mEndingAnims, mEndingAnims.get(i).endAnimation(this);內部,會將動畫移除mAnimations,回調動畫監聽接口onAnimationEnd;以及重置各種標志變量。46-48行:如果mAnimations不為null,則再次調用scheduleAnimation();哈哈,終於終於發現了,每隔多少幀調用一次動畫的地方了~~尼瑪這個scheduleAnimation,不就是animationHandler的 run方法調用的麼~~
搜噶,到此~~我們的屬性動畫的流程已經完美跑通了~~~
對了,看完以後,和我們文章開始的預期符合麼,其實我覺得差不多~~
其實看源碼的目的,最終就是為了總結,尼瑪這麼長的代碼誰也記不住。。。所以看完記得總結:
ofInt中實例化了一個ObjectAnimator對象,然後設置了target,propName,values(PropertyValuesHolder) ;然後分別在setInterpolator,setDuration設置了Interpolator
和duration。其中setEvaluator是給PropertyValuesHolder,以及keyframeSet設置估值算法。
PropertyValueHolder實際上是IntPropertyValueHolder類型對象,包含propName,valueType,keyframeSet .
keyframeset中存了Keyframe集合,keyframe中存儲了(fraction , valuetype , value , hasValue)。
上述其實都是設置各種值什麼的。真正核心要看start~
start()中:
首先,步驟1:更新動畫各種狀態,然後初步計算fraction為(currentTime - mStartTime) / mDuration;然後將這個fraction交給我們的插值器計算後得到新的fraction,再將新的fraction交給我們的估值算法,估值算法根據開始、結束、fraction得到當前屬性(動畫作用的屬性)應該的值,最大調用反射進行設置;
當然了:start中還會根據動畫的狀態,如果沒有結束,不斷的調用scheduleAnimation();該方法內部利用mChoreographer不斷的去重復我們的上述步驟1。
好了,順便說一句,在看源碼的時候,一定要注意,你點進去的有可能不是真正運行時調用的,記得查看該方法子類,比如我們查看ObjectAnimator的方法,可能我們某個方法會跟到其父類ValueAnimator的方法,但是記得查看ObjectAnimator是否復寫了該方法~~如果復寫了,你該看的應該是ObjectAnimator的方法~~~
說到老照片,很多人就會想起兒時的照片。沒錯,老照片就是這樣的,我稱之為情懷濾鏡。先說一下Android圖像矩陣處理(圖片來源 慕課網)也就是說,每一個矩陣都對應著一個唯一
啥也不說看圖: 點擊後效果: 代碼:主方法: package com.text.ac; import java.util.Calend
Android基礎入門教程——10.7 WindowManager(窗口管理服務)標簽(空格分隔): Android基礎入門教程本節引言: 本節給
Android Vitamio 庫Vitamio是一個android和ios上基於FFmpeg的開源項目。Vitamio為我們提供了一個清潔、簡單、全面、真
Sharing Simple DataOne of the great