Android開發學習之路-RecyclerView的Item自定義動畫及DefaultItemAnimator源碼分析

這是關於RecyclerView的第二篇,說的是如何自定義Item動畫,可是請注意,本文不包含動畫的具體實現方法,只是告訴你們如何去自定義動畫,如何去參考源代碼。設計模式

咱們知道,RecyclerView默認會使用DefaultItemAnimator,因此若是咱們須要自定義動畫,那麼應該好好的讀讀這個類的源代碼,這樣不單單是學習怎麼自定義,還要學習Android的設計模式。網絡

先弄明白一件事,DefaultItemAnimator繼承自SimpleItemAnimator,SimpleItemAnimator繼承自RecyclerView.ItemAnimator,因此若是須要自定義動畫,最簡單的方法是繼承SimpleItemAnimator。其次,動畫的類型有四種,分別是Add、Remove、Move以及Change,這裏咱們只列舉Remove,觸類旁通。app

咱們先看SimpleItemAnimator中的源碼,在SimpleItemAnimator中有幾個重要的方法:ide

 1 @Override
 2     public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
 3             @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
 4         int oldLeft = preLayoutInfo.left;
 5         int oldTop = preLayoutInfo.top;
 6         View disappearingItemView = viewHolder.itemView;
 7         int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
 8         int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
 9         if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
10             disappearingItemView.layout(newLeft, newTop,
11                     newLeft + disappearingItemView.getWidth(),
12                     newTop + disappearingItemView.getHeight());
13             if (DEBUG) {
14                 Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
15             }
16             return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
17         } else {
18             if (DEBUG) {
19                 Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
20             }
21             return animateRemove(viewHolder);
22         }
23     }

解析:這個函數是重寫RecyclerView.ItemAnimator的,接口中參數分別是ViewHolder、prelayoutInfo以及postLayoutInfo,第一個參數是指Item的ViewHolder,能夠經過這個對象的itemView來獲取它的View,第二個參數是指Item刪除前的位置信息,第三個是指新的位置信息。再接下來會判斷ViewHolder是否已經被移除以及位置是否發生變化,而後在調用animateRemove這個抽象方法,若是咱們要自定義動畫,就須要去實現它(回調思想)。函數

1 public final void dispatchRemoveStarting(ViewHolder item) {
2         onRemoveStarting(item);
3     }
1 public void onRemoveStarting(ViewHolder item) {
2     }

解析:dispatchRemoveStaring個是一個final方法,也就是不能被重寫,若是咱們須要處理一些在Remove開始的時候的邏輯,咱們就須要在animateRemove方法中調用這個方法,這個方法會執行一個onRemoveStaring方法,這個方法就容許咱們重寫,因此邏輯應該寫在onRemoveStaring中,當咱們調用dispatchRemoveStaring的時候,onRemoveStaring就會被執行。post

這裏只說了兩個,可是,加上其餘動做的就不僅是兩個啦。。。學習

因此,當咱們繼承了SimpleItemAnimator的時候,須要實現裏面的一些方法,通常有以下這些:動畫

① animateRemove(Add、Move和Change):這些方法會在動畫發生的時候回調,通常會在這個方法中用列表記錄每一個Item的動畫以及屬性spa

② endAnimation、endAnimations:分別是在一個Item或是多個Item須要當即中止的時候回調設計

③ isRunning:若是須要順暢滑動的時候,必需要重寫這個方法,不少時候好比在網絡加載的時候滑動卡頓就是這個方法邏輯不對

④ run'PendingAnimations:這是最重要的一個方法。由於animateDisappearence等方法返回的是animateRemove等方法返回的值,而這個方法則是根據這些值來肯定是否有準備好的動畫須要播放,若是有,就會回調這個方法。在這個方法咱們須要處理每個動做(Remove、Add、Move以及Change)的動畫

因此,咱們的通常步驟就是:

①建立一個SimpleItemAnimator的子類

②建立每一個動做的動做列表

③重寫animateRemove等方法,當界面中有動做發生,這些函數會被回調,這裏進行記錄並返回true使得run'PendingAnimations開始執行

④重寫run'PendingAnimations,當③的方法返回true的時候,就認爲須要執行動畫,咱們須要把動畫執行的邏輯寫在這裏面

⑤重寫isRunning,提供動畫播放狀態,通常是返回動做列表是否爲空

⑥若是有須要,重寫endAnimation、endAnimations、onRemoveFinish等方法

具體的步驟有了,可是咱們還不清楚該怎麼構建它,不用着急,爲了方便咱們,谷歌其實已經提供了DefaultItemAnimator,咱們能夠參考一些它的源碼,沒有人講的比源碼有道理,咱們須要的是有足夠的耐心!

DefaultItemAnimator中定義了一些ArrayList來存放動做的信息,以下:

private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
1 @Override
2     public boolean animateRemove(final ViewHolder holder) {
3         resetAnimation(holder);
4         mPendingRemovals.add(holder);
5         return true;
6     }

解析:能夠看到animatorRemove方法直接是把viewholder加入列表中,而後返回true

 1 @Override
 2     public void runPendingAnimations() {
 3         boolean removalsPending = !mPendingRemovals.isEmpty();
 4         boolean movesPending = !mPendingMoves.isEmpty();
 5         boolean changesPending = !mPendingChanges.isEmpty();
 6         boolean additionsPending = !mPendingAdditions.isEmpty();
 7         if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
 8             // nothing to animate
 9             return;
10         }
11         // First, remove stuff
12         for (ViewHolder holder : mPendingRemovals) {
13             animateRemoveImpl(holder);
14         }
15         mPendingRemovals.clear();
16         // Next, move stuff
17         ......
18         // Next, change stuff, to run in parallel with move animations
19         ......
20         // Next, add stuff
21         ......
22     }            

解析:根據上面能夠知道,runPendingAnimations會執行,可看到,在這個方法中遍歷了動做列表,並讓每一個Item都執行了animatorRemoveImpl方法,其餘動做的方法暫時先省略,有興趣的能夠自行閱讀。

 1 private void animateRemoveImpl(final ViewHolder holder) {
 2         final View view = holder.itemView;
 3         final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
 4         mRemoveAnimations.add(holder);
 5         animation.setDuration(getRemoveDuration())
 6                 .alpha(0).setListener(new VpaListenerAdapter() {
 7             @Override
 8             public void onAnimationStart(View view) {
 9                 dispatchRemoveStarting(holder);
10             }
11 
12             @Override
13             public void onAnimationEnd(View view) {
14                 animation.setListener(null);
15                 ViewCompat.setAlpha(view, 1);
16                 dispatchRemoveFinished(holder);
17                 mRemoveAnimations.remove(holder);
18                 dispatchFinishedWhenDone();
19             }
20         }).start();
21     }

解析:能夠看到animatorRemoveImpl方法中實現了整個動畫的具體邏輯,具體怎麼作不在本文範圍中,在咱們執行了動畫以後,也就是在動畫的Listener中的onAnimatorEnd中調用了dispatchRemoveFinish,還記得這個方法嗎,它會執行onRemoveFinish方法,onRemoveFinish方法是能夠供給咱們重寫的。而後把item移除動做列表。

 1 @Override
 2     public boolean isRunning() {
 3         return (!mPendingAdditions.isEmpty() ||
 4                 !mPendingChanges.isEmpty() ||
 5                 !mPendingMoves.isEmpty() ||
 6                 !mPendingRemovals.isEmpty() ||
 7                 !mMoveAnimations.isEmpty() ||
 8                 !mRemoveAnimations.isEmpty() ||
 9                 !mAddAnimations.isEmpty() ||
10                 !mChangeAnimations.isEmpty() ||
11                 !mMovesList.isEmpty() ||
12                 !mAdditionsList.isEmpty() ||
13                 !mChangesList.isEmpty());
14     }

解析:isRunning方法其實就是根據動做列表是否爲空來返回結果

還有其餘一些函數能夠本身閱讀源代碼。

相關文章
相關標籤/搜索