這是關於RecyclerView的第二篇,說的是如何自定義Item動畫,但是請注意,本文不包含動畫的具體實現方法,只是告訴大家如何去自定義動畫,如何去參考源代碼。
我們知道,RecyclerView默認會使用DefaultItemAnimator,所以如果我們需要自定義動畫,那么應該好好的讀讀這個類的源代碼,這樣不僅僅是學習怎么自定義,還要學習Android的設計模式。
先弄明白一件事,DefaultItemAnimator繼承自SimpleItemAnimator,SimpleItemAnimator繼承自RecyclerView.ItemAnimator,所以如果需要自定義動畫,最簡單的方法是繼承SimpleItemAnimator。其次,動畫的類型有四種,分別是Add、Remove、Move以及Change,這里我們只列舉Remove,舉一反三。
我們先看SimpleItemAnimator中的源碼,在SimpleItemAnimator中有幾個重要的方法:
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就會被執行。
這里只說了兩個,但是,加上其他動作的就不只是兩個啦。。。
所以,當我們繼承了SimpleItemAnimator的時候,需要實現里面的一些方法,一般有如下這些:
① animateRemove(Add、Move和Change):這些方法會在動畫發生的時候回調,一般會在這個方法中用列表記錄每個Item的動畫以及屬性
② 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方法其實就是根據動作列表是否為空來返回結果
還有其他一些函數可以自己閱讀源代碼。