本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈node
此次想來說講 View.animate(),這是一種超好用的動畫實現方式,用這種方式來實現經常使用的動畫效果很是方便,但在某些場景下會有一個坑,因此此次就來梳理一下它的原理。android
首先,先來看一段代碼:微信
mView.animate().sacleX(1.2f).scaleY(1.2f).alpha(0.5f).setDuration(1000).start();
可能有些人還沒接觸過這個,但並不妨礙首次理解上述代碼。單從方法名上來看,上述代碼就是一個實現了持續 1s 的放大 & 透明度結合的動畫,是否是發現使用起來特別簡單,一行代碼就搞定。數據結構
固然,上述的動畫效果也能夠經過 ValueAnimator 或 ObjectAnimator 來實現,只是可能無法像上述同樣一行代碼就搞定。若是用 Animation 來實現,那麼須要的代碼就更多了。ide
因此,咱們的問題就來了:函數
Q1:動畫基本能夠分爲 Animator 和 Animation 兩大類,而 View.animate() 返回的是一個 ViewPropertyAnimator 類型的對象,這個類並無繼承自任何類,那麼它實現動畫的原理又是什麼呢?單從命名上看好像是經過 Animator 實現,那麼真的是這樣麼?post
Q2:開頭說了,使用這種方式實現的動畫在某些場景下會有一個坑,這個坑又是什麼,是在什麼場景下的呢?學習
好了,下面就開始來跟着源碼一塊兒學習吧:動畫
ps:本篇閱讀的源碼版本都是 android-25,版本不同,源碼可能會有些許差異,大夥本身過的時候注意一下。ui
那麼,源碼閱讀的着手點就跟以前幾篇分析動畫的同樣,從 start()
開始一步步跟蹤下去就好了。
//ViewPropertyAnimator#start() public void start() { mView.removeCallbacks(mAnimationStarter); startAnimation(); }
代碼不多就兩行,第二行是調用了一個方法,看方法名能夠猜想應該是去處理動畫開始的工做,那麼在動畫開始前還移除了一個回調,但要搞清楚第一行的代碼是幹嗎用的,咱們得先知道兩個變量的含義,首先是第一個 mView:
//ViewPropertyAnimator構造函數 ViewPropertyAnimator(View view) { mView = view; view.ensureTransformationInfo(); }
mView 是一個成員變量,在構造函數中被賦值,還記得吧,要用這種方式實現動畫時,都得先調用 View.animate() 來創造一個 ViewPropertyAnimator 對象,因此去 View 的 animate()
方法裏瞧瞧:
//View#animate() public ViewPropertyAnimator animate() { if (mAnimator == null) { mAnimator = new ViewPropertyAnimator(this); } return mAnimator; }
這個方法裏會去建立一個 ViewPropertyAnimator 對象,並將 View 自身 this 做爲參數傳遞進去,也就是說,在 ViewPropertyAnimator 裏的 mView 變量其實指向的就是要進行動畫的那個 View。
知道了 mView 其實就是須要進行動畫的那個 View 後,接下去來看看另外一個變量 mAnimationStarter 是什麼了:
//ViewPropertyAnimator.mAnimationnStarter private Runnable mAnimationStarter = new Runnable() { @Override public void run() { startAnimation(); } };
這個 Runnable 就是一個啓動動畫的工做,emmm,這樣就有點奇怪了,咱們再回過頭來看看 start()
方法:
//ViewPropertyAnimator#start() public void start() { mView.removeCallbacks(mAnimationStarter); startAnimation(); }
爲何明明方法的第二行就會去執行 startAnimation()
了,第一行卻又要去取消一個執行 startAnimation()
的 Runnable 呢?
只能說明,在咱們調用 start()
以前,ViewPropertyAnimator 內部就已經預先安排了一個會執行 startAnimation()
的 Runnable 進入待執行狀態,因此在調用了 start()
以後先去取消這個 Runnable 纔會有意義。
那麼,又是哪裏會去觸發安排一個 Runnable 呢?
回頭再看看咱們使用這種方式來實現動畫效果是怎麼用的:
mView.animate().sacleX(1.2f).scaleY(1.2f).alpha(0.5f).setDuration(1000).start();
首先,經過 View.animate()
先建立一個 ViewPropertyAnimator 對象,中間設置了一系列動畫行爲,最後才調用了 start()
。那麼,有機會去觸發安排一個待執行的 Runnable 操做也只能發生在中間的這些方法裏了,那麼咱們選擇一個跟進去看看,scaleX()
:
//ViewPropertyAnimator#scaleX() public ViewPropertyAnimator scaleX(float value) { animateProperty(SCALE_X, value); return this; }
繼續跟進去看看:
//ViewPropertyAnimator#animateProperty() private void animateProperty(int constantName, float toValue) { float fromValue = getValue(constantName); float deltaValue = toValue - fromValue; animatePropertyBy(constantName, fromValue, deltaValue); }
至於各個參數是什麼意思,咱們後面再來分析,目前咱們是想驗證是否是這些封裝好的動畫接口內部會去觸發一個待執行的 Runnable 操做,因此優先繼續跟蹤下去:
//ViewPropertyAnimator#animatePropertyBy() private void animatePropertyBy(int constantName, float startValue, float byValue){ ... mView.removeCallbacks(mAnimationStarter); mView.postOnAnimation(mAnimationStarter); }
終於找到了,並且不只僅是 scaleX()
方法,其餘封裝好的動畫接口如 scaleY()
,alpha()
,translationX()
等等全部這一系列的方法內部最終都會走到 animatePropertyBy()
裏去。而在這個方法最後都會先將待執行的 Runnable 先移除掉,再從新 post。
要理解這麼作的用意,得先明白 View 的這兩個方法:removeCallbacks()
,postOnAnimation()
是幹嗎用的。這裏我就不跟下去了,直接給大夥說下結論:
經過 postOnAnimation()
傳進去的 Runnable 並不會被立刻執行,而是要等到下一個屏幕刷新信號來的時候纔會被取出來執行。
那麼,將這些串起來,也就是說,僅僅只是 View.animate().scaleX()
這樣使用時,就算不主動調用 start()
,其實內部也會自動安排一個 Runnable,最遲在下一個屏幕刷新信號來的時候,就會自動去調用 startAnimation()
來啓動動畫。
但若是主動調用了 start()
,內部就須要先將安排好的 Runnable 操做取消掉,而後直接調用 startAnimation()
來啓動動畫。
那麼,接下去就來看看是如何啓動動畫的,startAnimation()
:
//ViewPropertyAnimator#startAnimation() private void startAnimation() { //1. 這裏我還沒搞懂,也不清楚什麼場景下會知足這裏的條件,直接 return。因此,本篇接下去的分析都是基於假設會直接跳過這裏,後面若是搞懂了再來填坑。 if (mRTBackend != null && mRTBackend.startAnimation(this)) { return; } ... //2. 建立一個 0.0-1.0 變化的 ValueAnimator ValueAnimator animator = ValueAnimator.ofFloat(1.0f); //3. 將當前 mPengingAnimations 裏保存的一系列動畫全都取出來,做爲同一組一塊兒執行一塊兒結束的動畫 ArrayList<NameValuesHolder> nameValueList = (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); ... //4. 建立一個新的 PropertyBundle 來保存這一組動畫,以ValueAnimator做爲key來區分 mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); //5. 提供動畫開始前,結束後的操做回調 if (mPendingSetupAction != null) { mAnimatorSetupMap.put(animator, mPendingSetupAction); mPendingSetupAction = null; } ... //6. 對ValueAnimator進行 Listener、StartDelay、Duration、Interpolator 的設置 animator.addUpdateListener(mAnimatorEventListener); animator.addListener(mAnimatorEventListener); ... //7. 啓用ValueAnimator.start() animator.start(); }
上述代碼能夠先不用細看,咱們稍後再來一塊一塊慢慢過,我已經將整個方法裏作的事大概劃分紅了 7 件,首先有一點須要提一下,方法內實際上是經過 ValueAnimator 來實現的。
上一篇博客屬性動畫 ValueAnimator 運行原理全解析中,咱們已經將 ValueAnimator 的運行原理分析完了,感興趣的能夠回去看看,這裏大概提幾點結論:
ValueAnimator 內部其實並無進行任何 ui 操做,它只是提供了一種機制,能夠根據設定的幾個數值,如 0-100,內部本身在每一幀內,根據當前時間,第一幀的時間,持續時長,以及插值器規則,估值器規則來計算出在當前幀內動畫的進度並映射到設定的數值區間,如 0-100 區間內映射以後的數值應該是多少。
既然 ValueAnimator 並無進行任何 ui 操做,那麼要用它來實現動畫效果,只能本身在 ValueAnimator 提供的每一幀的回調裏(AnimatorUpdateListener),本身取得 ValueAnimator 計算出的數值,來自行應用到須要進行動畫效果的那個 View 上。
想一想本身使用 ValueAnimator 的時候是否是這樣,咱們並無將 View 做爲參數傳遞給 ValueAnimator,因此它內部也就沒有持有任何 View 的引用,天然作不了任何 ui 操做。
因此看看 startAnimation()
方法裏的,我標出來的第 二、六、7點:
//ViewPropertyAnimator#startAnimation() private void startAnimation() { ... //2. 建立一個 0.0-1.0 變化的 ValueAnimator ValueAnimator animator = ValueAnimator.ofFloat(1.0f); ... //6. 對ValueAnimator進行 Listener、StartDelay、Duration、Interpolator 的設置 animator.addUpdateListener(mAnimatorEventListener); animator.addListener(mAnimatorEventListener); ... //7. 啓用ValueAnimator.start() animator.start(); }
因此,ViewPropertyAnimator 實際上是經過 ValueAnimator.ofFloat(1.0f)
,也就是藉助 ValueAnimator 的機制,來計算每一幀動畫進度在 0-1 內對應的數值。而後在它的每一幀的回調裏再去進行 view 的 ui 操做來達到動畫效果,那麼 ui 操做也就是在 mAnimatorEventListener 裏作的事了,跟進去看看:
//ViewPropertyAnimator.mAnimatorEventListener private AnimatorEventListener mAnimatorEventListener = new AnimatorEventListener(); private class AnimatorEventListener implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { ... @Override public void onAnimationUpdate(ValueAnimator animation) { ... //1. 取出 ValueAnimator 計算出的當前幀的動畫進度 float fraction = animation.getAnimatedFraction(); //2. 根據取得的動畫進度,進行一系列view的ui操做,來達到動畫效果 ... } }
省略了絕大部分代碼,等會會再來慢慢過,這樣省略後比較容易梳理出整個流程,優先將流程梳理清楚,再來分析每一個步驟具體乾的活。
因此,能夠看到,ViewPropertyAnimator 確實是在 ValueAnimator 的每一幀的回調中,取得 VauleAnimator 機制計算出來的動畫進度值,而後自行進行 ui 操做來達到動畫效果。
那麼,到這裏,整個流程就已經梳理出來了,咱們先來梳理一下目前的信息:
經過 View.animate().scaleX(1.2f).start()
實現的動畫,若是外部沒有手動調用 start()
方法,那麼 ViewPropertyAnimator 內部最遲會在下一幀的時候自動調用 startAnimation()
來啓動動畫。
ViewPropertyAnimator 實現下一幀內自動啓動動畫是經過 View.postOnAnimation()
實現,View 的這個方法會將傳遞進來的 Runnable 等到下一幀的時候再去執行。
若是外部手動調用了 start()
,那麼內部會先將第 2 步中安排的自動啓動動畫的 Runnable 取消掉,而後直接調用 startAnimation()
啓動動畫。
startAnimation()
啓動動畫,其實是藉助 ValueAnimator 的機制,在 onAnimationUpdate()
裏取得每一幀內的動畫進度時,再自行進行對應的 ui 操做來達到動畫效果。
ValueAnimator 只是會根據當前時間,動畫第一幀時間,持續時長,插值器規則,估值器規則等來計算每一幀內的當前動畫進度值,而後根據關鍵幀機制來映射到設定的範圍內的數值,最後經過每一幀的進度回調,供外部使用,它自己並無任何 ui 操做(詳情可看上一篇博客)。
好了,流程上已經梳理清理了,接下去就是細節問題了,ViewPropertyAnimator 取得了每一幀對應的動畫進度時又是如何進行的 ui 操做的呢?View.animate()
後面是支持一系列的動畫操做,如 scaleX()
,alpha()
等一塊兒執行的,那麼內部又是如何區分,維護的呢?
咱們仍是按照流程來一步步詳細的分析,View.animate() 方式實現的動畫,流程上是設置動畫行爲--啓動動畫--每一幀進度回調中進行ui操做。因此,下面就先看看第一個步驟,跟着 scaleX()
進去看看:
//ViewPropertyAnimator#scaleX() public ViewPropertyAnimator scaleX(float value) { //1. 第一個參數用於區分不一樣的動畫,第二個參數設定動畫最後一幀的值 animateProperty(SCALE_X, value); return this; } //ViewPropertyAnimator#animateProperty() private void animateProperty(int constantName, float toValue) { //2. 第一步先取得該種動畫行爲下的默認第一幀值,最後一幀值就是參數傳遞進來 float fromValue = getValue(constantName); //3. 計算出動畫的變化數值 float deltaValue = toValue - fromValue; animatePropertyBy(constantName, fromValue, deltaValue); } //ViewPropertyAnimator#getValue() private float getValue(int propertyConstant) { final RenderNode node = mView.mRenderNode; switch (propertyConstant) { ... //4. 直接經過 getScaleX() 取得當前 view 的默認屬性值 case SCALE_X: return node.getScaleX(); ... } return 0; }
上述代碼做用,其實也就只是取得對應動畫行爲下的第一幀的屬性值,而後根據設定的最後一幀屬性值來計算出動畫變化的數值,最終做爲參數傳遞給 animatePropertyBy()
,因此最關鍵的任務確定在這個方法裏,但要捋清楚這個方法裏的代碼前,還須要先了解一些變量以及內部類的含義:
ViewPropertyAnimator 內部有兩個數據結構類 NameValuesHolder 和 PropertyBundle,都是用於存儲各類動畫信息的,除此以外,還有一系列成員變量的列表,如 mPendingAnimations,mAnimatorMap 等。要搞清楚這些的含義,還得先搞懂 View.animate()
是支持如何使用的。
這麼說吧,仍是拿開頭的示例代碼來講明:
mView.animate().scaleX(1.2f).scaleY(1.2f).alpha(0.5f).setDuration(1000).start();
ViewPropertyAnimator 亮點就是支持鏈式調用一系列不一樣的動畫一塊兒執行,因此須要注意一點,一旦像上述那樣使用,那麼設定的這一系列動畫就會是一塊兒執行一塊兒結束的。
那麼,有可能存在這種場景:先設置了一系列動畫執行,若是在這一系列的動畫執行結束前,又經過 View.animate()
設置了另一系列一塊兒執行的動畫效果,那麼這時就會有兩組動畫都在運行中,每組動畫均可能含有多種類型的動畫,因此內部就須要以每組爲單位來保存信息,確保每組動畫能夠互不干擾,這就是 PropertyBundle 這個類的做用了:
//ViewPropertyAnimator$PropertyBundle private static class PropertyBundle { int mPropertyMask; ArrayList<NameValuesHolder> mNameValuesHolder; ... }
這樣解釋完,再來看這個類,這樣理解兩個成員變量的含義就容易多了,首先 mNameValuesHolder 是一個 ArrayList 對象,顯然就是用來存儲這一組動畫裏的那一系列不一樣類型的動畫;那具體存在列表裏都有哪些類型的動畫呢,就是另外一個成員變量 mPropertyMask 來進行標誌了。
而列表裏存的這一組動畫裏的不一樣類型的動畫,因此 NamaValuesHolder 這個類的做用就是用於區分各類不一樣類型的動畫了:
//ViewPropertyAnimator$NameValuesHolder static class NameValuesHolder { int mNameConstant; float mFromValue; float mDeltaValue; ... }
第一個成員變量 mNameConstant 就是用於區分不一樣類型的動畫,在 ViewPropertyAnimator 內部定義了一系列經常使用動畫的常量,mNameConstant 這個變量的取值就在這些常量中,如開頭出現 SCALE_X。而另外兩個變量表示的就是這種類型的動畫要進行變化的數值信息。
另外,ViewPropertyAnimator 支持設置一系列不一樣類型的動畫,那麼它是以什麼爲依據來決定哪一系列的動畫做爲第一組,哪一系列做爲第二組呢?其實很簡單,就是以 startAnimation()
被調用爲依據。那麼,成員變量 mPendingAnimations 的做用也就出來了。
每一次調用 scaleX()
等等之類的方法時,都會建立一個 NameValuesHolder 對象來保存對應這種類型的動畫信息,而後保存在 mPendingAnimations 列表中。scaleY()
等這些方法不斷被調用,mPendingAnimations 就會保存愈來愈多的待執行的不一樣類型的動畫。而一旦 startAnimation()
方法被調用時,就會將當前 mPendingAnimations 列表裏存的這一系列動畫做爲同一組一塊兒執行一塊兒結束的動畫保存到一個新的 PropertyBundle 對象裏。而後清空 mPendingAnimations,直到下一次 startAnimation()
被調用時,再次將 mPendingAnimations 中新保存的一系列動畫做爲另一組動畫保存到新的 PropertyBundle 中去。
那麼,最後還須要有一個變量來保存並區分這一組一組的動畫,這就是 mAnimatorMap 變量的做用了。
private HashMap<Animator, PropertyBundle> mAnimatorMap = new HashMap<Animator, PropertyBundle>();
看一下定義,沒錯吧,PropertyBundle 保存的是一組動畫裏一塊兒開始一塊兒結束的一系列動畫,因此 mAnimatorMap 是以 Animator 爲 Key 區分每一組動畫的。
捋清楚了這些內部類和變量的做用,咱們下面再來看以前分析的調用了 scaleX()
後,內部跟到了 animatePropertyBy()
,那麼咱們繼續跟下去看看:
//ViewPropertyAnimator#animatePropertyBy() private void animatePropertyBy(int constantName, float startValue, float byValue) { //1. mAnimatorMap 存放着一組一組正在運行中的動畫 if (mAnimatorMap.size() > 0) { Animator animatorToCancel = null; Set<Animator> animatorSet = mAnimatorMap.keySet(); for (Animator runningAnim : animatorSet) { // 2. bundle 保存着當前這一組動畫裏的一系列正在運行中的不一樣類型的動畫 PropertyBundle bundle = mAnimatorMap.get(runningAnim); if (bundle.cancel(constantName)) { if (bundle.mPropertyMask == NONE) { animatorToCancel = runningAnim; break; } } } if (animatorToCancel != null) { animatorToCancel.cancel(); } } // 3. 因此上述1 2步的工做就是要將當前constantName類型的動畫取消掉 //4. 建立一個 NameValuesHolder 對象用於保存當前constantName類型的動畫信息 NameValuesHolder nameValuePair = new NameValuesHolder(constantName, startValue, byValue); //5. 將該類型的動畫信息保存到 mPendingAnimations 中 mPendingAnimations.add(nameValuePair); //6. 安排一個自動開啓動畫的Runnable,最遲在下一幀觸發 mView.removeCallbacks(mAnimationStarter); mView.postOnAnimation(mAnimationStarter); }
上述代碼裏從第 4-6 的步驟應該都清楚了吧,每次調用 scaleX()
之類的動畫,內部須要先建立一個 NameValuesHolder 對象來保存該類型的動畫行爲(第4步),而後將該類型動畫添加到 mPendingAnimations 列表中(第5步)來做爲組成一系列一塊兒開始一塊兒結束的動畫,最後會自動安排一個最遲在下一幀內自動啓動動畫的 Runnable(第6步)。
那麼第 1-3 步又是幹嗎的呢?
是這樣的,上面說過,可能會存在一組一組都在運行中的動畫,每一組都有一系列不一樣類型的動畫,那麼就有可能出現同一種類型的動畫,好比 scaleX()
,既在第一組裏,又在第二組裏。很顯然,ViewPropertyAnimator 裏的全部動畫都是做用於同一個 View 上,而不一樣組的動畫又有可能同一時刻都在運行中,那麼,一個 View 的同一種類型動畫有可能在同一時刻被執行兩次麼?說得白一點,一個 View 的大小若是在同一幀內先放大 1.2 倍,同時又放大 1.5 倍,那這個 View 呈現出來的效果確定特別錯亂。
因此,ViewPropertyAnimator 裏全部的動畫,在同一時刻,同一類型的動畫只支持只有一個處於正在運行中的狀態,這也就是第 1-3 步的意義,它須要去遍歷當前每一組裏的每個動畫,若是類型跟當前設定的動畫類型一致,那麼就將以前的動畫取消掉,以最近設定的此次爲準。
好了,scaleX()
這些設定動畫的行爲,內部實現的細節咱們已經分析完了,下面就繼續看看下一個流程,啓動動畫裏都幹了啥,startAnimation()
:
//ViewPropertyAnimator#startAnimation() private void startAnimation() { //1. 這裏我還沒搞懂,也不清楚什麼場景下會知足這裏的條件,直接 return。因此,本篇接下去的分析都是基於假設會直接跳過這裏,後面若是搞懂了再來填坑。 if (mRTBackend != null && mRTBackend.startAnimation(this)) { return; } mView.setHasTransientState(true); //2. 建立一個 0.0-1.0 變化的 ValueAnimator ValueAnimator animator = ValueAnimator.ofFloat(1.0f); //3. 將當前 mPengingAnimations 裏保存的一系列動畫全都取出來,做爲同一組一塊兒執行一塊兒結束的動畫 ArrayList<NameValuesHolder> nameValueList = (ArrayList<NameValuesHolder>) mPendingAnimations.clone(); mPendingAnimations.clear(); int propertyMask = 0; int propertyCount = nameValueList.size(); //3.1 遍歷這一系列動畫,將這些動畫都有哪些類型的動畫標誌出來 for (int i = 0; i < propertyCount; ++i) { NameValuesHolder nameValuesHolder = nameValueList.get(i); propertyMask |= nameValuesHolder.mNameConstant; } //4. 建立一個新的 PropertyBundle 來保存這一組動畫,以ValueAnimator做爲key來區分 mAnimatorMap.put(animator, new PropertyBundle(propertyMask, nameValueList)); //5. 提供動畫開始前,結束後的操做回調 if (mPendingSetupAction != null) { mAnimatorSetupMap.put(animator, mPendingSetupAction); mPendingSetupAction = null; } if (mPendingCleanupAction != null) { mAnimatorCleanupMap.put(animator, mPendingCleanupAction); mPendingCleanupAction = null; } if (mPendingOnStartAction != null) { mAnimatorOnStartMap.put(animator, mPendingOnStartAction); mPendingOnStartAction = null; } if (mPendingOnEndAction != null) { mAnimatorOnEndMap.put(animator, mPendingOnEndAction); mPendingOnEndAction = null; } //6. 對ValueAnimator進行 Listener、StartDelay、Duration、Interpolator 的設置 animator.addUpdateListener(mAnimatorEventListener); animator.addListener(mAnimatorEventListener); if (mStartDelaySet) { animator.setStartDelay(mStartDelay); } if (mDurationSet) { animator.setDuration(mDuration); } if (mInterpolatorSet) { animator.setInterpolator(mInterpolator); } //7. 啓用ValueAnimator.start() animator.start(); }
第 1 步我還沒搞清楚,就先暫時跳過吧。
第 2-4 步就是咱們上面有說過的,當 startAnimation()
被調用時,將當前保存在 mPendingAnimations 列表裏全部的動畫都做爲同一組一塊兒開始一塊兒結束的動畫,保存到一個新的 PropertyBundle 對象中,每一組動畫何時開始,結束,以及每一幀的進度都是藉助 ValueAnimator 機制實現,因此每一組動畫就以不一樣的 ValueAnimator 對象做爲 key 值保存到 mAnimatorMap 中相戶區分,獨立出來。
第 5 步是 ViewPropertyAnimator 支持的接口,都是供外部根據須要使用,好比 mPendingOnStartAction 就是表示會在這一組動畫開始的時候被執行,時機跟 onAnimationStart()
相同,外部使用的時候調用 withStartAction()
就能夠了。那麼爲何須要提供這樣的接口呢?
這是由於,若是咱們想要在動畫開始或結束的時候作一些事,若是咱們是這樣使用:
mView.animate().scaleX(1.2f) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { //do something } }).start();
沒錯,這樣寫的話,確實能夠實如今動畫前去執行咱們指定的工做。但這樣會有一個問題,由於 ViewPropertyAnimator 動畫是支持多組動畫同時進行中的,若是像上面這樣寫的話,那麼每一組動畫在開始以前就都會去回調這個 onAnimationStart()
方法,去作相同的事。
若是咱們只但願當前一組動畫去執行這些動畫開始前的工做,其餘組動畫不用去執行,那麼這時候就可使用 withStartAction()
來實現。
這就是第 5 步的用意。
第 6-7 步也就是對 ValueAnimator 作各類配置,如持續時長,延遲開始時間,插值器等等,最後調用 ValueAnimator.start()
來啓動。
好,啓動動畫的具體的工做咱們也分析完了,剩下最後一個流程了,在每一幀的回調中如何進行 ui 操做而且應用一系列的動畫。那麼,最後就看看 AnimatorEventListener:
//ViewPropertyAnimator.mAnimatorEventListener private class AnimatorEventListener implements Animator.AnimatorListener, ValueAnimator.AnimatorUpdateListener { ... @Override public void onAnimationUpdate(ValueAnimator animation) { //1. 取出跟當前 ValueAnimator 綁定的那一組動畫 PropertyBundle propertyBundle = mAnimatorMap.get(animation); ... //省略一堆沒看懂的代碼,跟硬件加速有關 ... //2. 獲取 ValueAnimator 機制計算出的當前幀的動畫進度 float fraction = animation.getAnimatedFraction(); int propertyMask = propertyBundle.mPropertyMask; ... //3. 遍歷這一組動畫裏的全部動畫,分別根據不一樣類型的動畫進行不一樣的 ui 操做來實現動畫效果 ArrayList<NameValuesHolder> valueList = propertyBundle.mNameValuesHolder; if (valueList != null) { int count = valueList.size(); for (int i = 0; i < count; ++i) { //3.1 取出第i個動畫 NameValuesHolder values = valueList.get(i); //3.2 根據ValueAnimator計算的當前幀動畫進度,以及第i個動畫的第一幀的屬性值和變化的值來計算出當前幀時的屬性值是多少 float value = values.mFromValue + fraction * values.mDeltaValue; //3.3 若是是 alpha 動畫,經過View的set方法來修改alpha值,不然調用setValue方法 if (values.mNameConstant == ALPHA) { alphaHandled = mView.setAlphaNoInvalidation(value); } else { setValue(values.mNameConstant, value); } } } //省略alpha動畫的一些輔助處理 ... //4. 進度回調,通知外部 if (mUpdateListener != null) { mUpdateListener.onAnimationUpdate(animation); } } }
這個方法作的事也很明確了,上述代碼中的註釋大概也說完了。也就是說 ViewPropertyAnimator 動畫內部在 ValueAnimator 的每一幀回調中,取出跟 ValueAnimator 綁定的那一組動畫,以及當前幀的動畫進度,而後再遍歷當前組的全部動畫,分別計算出每一個動畫當前幀的屬性值,若是不是 alpha 動畫的話,直接調用 setValue()
方法來進行 ui 操做達到動畫效果,若是是 alpha 動畫,則調用 view 的一個 set 方法來實現。
那麼,下面再繼續看看 setValue()
:
//ViewPropertyAnimator#setValue() private void setValue(int propertyConstant, float value) { final View.TransformationInfo info = mView.mTransformationInfo; final RenderNode renderNode = mView.mRenderNode; switch (propertyConstant) { case TRANSLATION_X: renderNode.setTranslationX(value); break; ... case SCALE_X: renderNode.setScaleX(value); break; case SCALE_Y: renderNode.setScaleY(value); break; ... } }
省略了一堆相似的代碼,這個方法裏,就所有都是根據不一樣類型的動畫,取得當前 View 的 mRenderNode 對象,而後分別調用相應的 setXXX 方法,如 SCALE_X 動畫,就調用 setScaleX()
方法來進行 ui 操做達到動畫效果。
以上,View.animate()
這種方式實現的動畫,也就是 ViewPropertyAnimator 動畫,的整個流程以及流程裏每一個步驟的工做,咱們到此就所有梳理清楚了。
最後,就來進行一下總結:
View.animate()
這種方式實現的動畫實際上是 ViewPropertyAnimator 動畫。
ViewPropertyAnimator 並非一種動畫,它沒有繼承自 Animator 或者 Animation,它其實只是一個封裝類,將經常使用的動畫封裝起來,對外提供方便使用的接口,內部藉助 ValueAnimator 機制。
ViewPropertyAnimator 動畫支持自動啓動動畫,若是外部沒有明確調用了 start()
,那麼內部會安排一個 Runnable 操做,最遲在下一幀內被執行,這個 Runnable 會去啓動動畫。
固然,若是外部手動調用了 start()
,那麼自動啓動動畫就沒意義了,內部會本身將其取消掉。
ViewPropertyAnimator 對外提供的使用動畫的接口很是方便,如 scaleX()
表示 x 的縮放動畫,alpha()
表示透明度動畫,並且支持鏈式調用。
因爲支持鏈式調用,因此它支持一系列動畫一塊兒開始,一塊兒執行,一塊兒結束。那麼當這一系列動畫還沒執行完又從新發起了另外一系列的動畫時,此時兩個系列動畫就須要分紅兩組,每一組動畫互不干擾,能夠同時執行。
但若是同一種類型的動畫,如 SCALE_X,在同一幀內分別在多組裏都存在,若是都同時運行的話,View 的狀態會變得很錯亂,因此 ViewPropertyAnimator 規定,同一種類型的動畫在同一時刻只能有一個在運行。
也就是說,多組動畫能夠處於並行狀態,可是它們內部的動畫是沒有交集的,若是有交集,好比 SCALE_X 動畫已經在運行中了,可是外部又新設置了一個新的 SCALE_X 動畫,那麼以前的那個動畫就會被取消掉,新的 SCALE_X 動畫纔會加入新的一組動畫中。
因爲內部是藉助 ValueAnimator 機制,因此在每一幀內均可以接收到回調,在回調中取得 ValueAnimator 計算出的當前幀的動畫進度。
取出當前幀的動畫進度後,就能夠遍歷跟當前 ValueAnimator 綁定的那一組動畫裏全部的動畫,分別根據每個動畫保存的信息,來計算出當前幀這個動畫的屬性值,而後調用 View 的 mRenderNode 對象的 setXXX 方法來修改屬性值,達到動畫效果。
還有一些細節並無概括到總結中,若是隻看總結的小夥伴,有時間仍是建議能夠慢慢跟着本文過一遍。
Q1:開頭說了,使用這種方式實現的動畫在某些場景下會有一個坑,這個坑又是什麼,是在什麼場景下的呢?
開頭說過使用這種方式實現的動畫,在某些場景下會存在一些坑。原本覺得這篇裏也能順便說清楚,但單單只是原理梳理下來,篇幅就很長了,那麼也當作遺留問題,留到以後的文章中來好好說下吧。能夠先說下是什麼坑:
若是當前界面有使用 RecyclerView 控件,而後又對它的 item 經過 View.animate()
方式實現了一些動畫效果,好比很常見的 Tv 應用的主頁,界面會有不少卡位,而後每一個卡位得到焦點時通常都須要放大的動畫,此時這個卡位就是 RecyclerView 的 item,放大動畫能夠經過 View.animate()
方式來實現。
在這種場景下,可能會存在這麼一種現象,當界面刷新時,若是此時有進行遙控器的方向鍵按鍵事件,那麼可能會有一些卡位的縮放動畫被中斷的現象。爲何會出現這種現象,再找個時間來梳理清楚。
Q2:View 的 mRenderNode 對象又是個什麼東西?它的 setXXX 方法又是如何修改 View 的屬性值來達到動畫效果的?
還有第二個遺留問題,雖然本篇梳理了 ViewPropertyAnimator 動畫的流程和原理,但到最後,咱們其實只知道它內部藉助了 ValueAnimator 機制來計算每一幀的動畫進度,而後在每一幀的回調中先獲取 View 的 mRenderNode 對象,再調用相應的 setXXX 方法來修改屬性值達到動畫效果。但這個 mRenderNode 是個什麼東西,又是如何修改 view 的狀態來達到動畫效果的這點就還須要找個時間來梳理了。
因此到最後,ViewPropertyAnimator 內部的流程和原理雖然已經清楚了,但具體要不要將這個動畫概括到屬性動畫中,我就不大清楚了。雖然它內部是藉助了 ViewAnimator 機制,但 ValueAnimator 其實並無任何的 ui 操做,ObjectAnimator 纔會去經過反射來調用相關的 setXXX 方法來修改屬性值,這個過程纔是 ui 操做,最後纔會有相應的動畫效果呈現出來。這點還有待繼續研究。
最近(2018-03)剛開通了公衆號,想激勵本身堅持寫做下去,初期主要分享原創的Android或Android-Tv方面的小知識,準備可能還有點不足,感興趣的能夠先點一波關注,謝謝支持~~