Android RecyclerView的Item自定義動畫及DefaultItemAnimator源碼分析
這是關(guān)于RecyclerView的第二篇,說的是如何自定義Item動畫,但是請注意,本文不包含動畫的具體實(shí)現(xiàn)方法,只是告訴大家如何去自定義動畫,如何去參考源代碼。
我們知道,RecyclerView默認(rèn)會使用DefaultItemAnimator,所以如果我們需要自定義動畫,那么應(yīng)該好好的讀讀這個類的源代碼,這樣不僅僅是學(xué)習(xí)怎么自定義,還要學(xué)習(xí)Android的設(shè)計(jì)模式。
先弄明白一件事,DefaultItemAnimator繼承自SimpleItemAnimator,SimpleItemAnimator繼承自RecyclerView.ItemAnimator,所以如果需要自定義動畫,最簡單的方法是繼承SimpleItemAnimator。其次,動畫的類型有四種,分別是Add、Remove、Move以及Change,這里我們只列舉Remove,舉一反三。
我們先看SimpleItemAnimator中的源碼,在SimpleItemAnimator中有幾個重要的方法:
@Override
public boolean animateDisappearance(@NonNull ViewHolder viewHolder,
@NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) {
int oldLeft = preLayoutInfo.left;
int oldTop = preLayoutInfo.top;
View disappearingItemView = viewHolder.itemView;
int newLeft = postLayoutInfo == null ? disappearingItemView.getLeft() : postLayoutInfo.left;
int newTop = postLayoutInfo == null ? disappearingItemView.getTop() : postLayoutInfo.top;
if (!viewHolder.isRemoved() && (oldLeft != newLeft || oldTop != newTop)) {
disappearingItemView.layout(newLeft, newTop,
newLeft + disappearingItemView.getWidth(),
newTop + disappearingItemView.getHeight());
if (DEBUG) {
Log.d(TAG, "DISAPPEARING: " + viewHolder + " with view " + disappearingItemView);
}
return animateMove(viewHolder, oldLeft, oldTop, newLeft, newTop);
} else {
if (DEBUG) {
Log.d(TAG, "REMOVED: " + viewHolder + " with view " + disappearingItemView);
}
return animateRemove(viewHolder);
}
}
解析:這個函數(shù)是重寫RecyclerView.ItemAnimator的,接口中參數(shù)分別是ViewHolder、prelayoutInfo以及postLayoutInfo,第一個參數(shù)是指Item的ViewHolder,可以通過這個對象的itemView來獲取它的View,第二個參數(shù)是指Item刪除前的位置信息,第三個是指新的位置信息。再接下來會判斷ViewHolder是否已經(jīng)被移除以及位置是否發(fā)生變化,然后在調(diào)用animateRemove這個抽象方法,如果我們要自定義動畫,就需要去實(shí)現(xiàn)它(回調(diào)思想)。
public final void dispatchRemoveStarting(ViewHolder item) {
onRemoveStarting(item);
}
public void onRemoveStarting(ViewHolder item) {
}
解析:dispatchRemoveStaring個是一個final方法,也就是不能被重寫,如果我們需要處理一些在Remove開始的時候的邏輯,我們就需要在animateRemove方法中調(diào)用這個方法,這個方法會執(zhí)行一個onRemoveStaring方法,這個方法就允許我們重寫,所以邏輯應(yīng)該寫在onRemoveStaring中,當(dāng)我們調(diào)用dispatchRemoveStaring的時候,onRemoveStaring就會被執(zhí)行。
這里只說了兩個,但是,加上其他動作的就不只是兩個啦。。。
所以,當(dāng)我們繼承了SimpleItemAnimator的時候,需要實(shí)現(xiàn)里面的一些方法,一般有如下這些:
① animateRemove(Add、Move和Change):這些方法會在動畫發(fā)生的時候回調(diào),一般會在這個方法中用列表記錄每個Item的動畫以及屬性
② endAnimation、endAnimations:分別是在一個Item或是多個Item需要立即停止的時候回調(diào)
③ isRunning:如果需要順暢滑動的時候,必須要重寫這個方法,很多時候比如在網(wǎng)絡(luò)加載的時候滑動卡頓就是這個方法邏輯不對
④ run'PendingAnimations:這是最重要的一個方法。因?yàn)閍nimateDisappearence等方法返回的是animateRemove等方法返回的值,而這個方法則是根據(jù)這些值來確定是否有準(zhǔn)備好的動畫需要播放,如果有,就會回調(diào)這個方法。在這個方法我們需要處理每一個動作(Remove、Add、Move以及Change)的動畫
所以,我們的一般步驟就是:
①創(chuàng)建一個SimpleItemAnimator的子類
②創(chuàng)建每個動作的動作列表
③重寫animateRemove等方法,當(dāng)界面中有動作發(fā)生,這些函數(shù)會被回調(diào),這里進(jìn)行記錄并返回true使得run'PendingAnimations開始執(zhí)行
④重寫run'PendingAnimations,當(dāng)③的方法返回true的時候,就認(rèn)為需要執(zhí)行動畫,我們需要把動畫執(zhí)行的邏輯寫在這里面
⑤重寫isRunning,提供動畫播放狀態(tài),一般是返回動作列表是否為空
⑥如果有需要,重寫endAnimation、endAnimations、onRemoveFinish等方法
具體的步驟有了,但是我們還不清楚該怎么構(gòu)建它,不用著急,為了方便我們,谷歌其實(shí)已經(jīng)提供了DefaultItemAnimator,我們可以參考一些它的源碼,沒有人講的比源碼有道理,我們需要的是有足夠的耐心!
DefaultItemAnimator中定義了一些ArrayList來存放動作的信息,如下:
private ArrayList<ViewHolder> mPendingRemovals = new ArrayList<>();
private ArrayList<ViewHolder> mRemoveAnimations = new ArrayList<>();
@Override
public boolean animateRemove(final ViewHolder holder) {
resetAnimation(holder);
mPendingRemovals.add(holder);
return true;
}
解析:可以看到animatorRemove方法直接是把viewholder加入列表中,然后返回true
@Override
public void runPendingAnimations() {
boolean removalsPending = !mPendingRemovals.isEmpty();
boolean movesPending = !mPendingMoves.isEmpty();
boolean changesPending = !mPendingChanges.isEmpty();
boolean additionsPending = !mPendingAdditions.isEmpty();
if (!removalsPending && !movesPending && !additionsPending && !changesPending) {
// nothing to animate
return;
}
// First, remove stuff
for (ViewHolder holder : mPendingRemovals) {
animateRemoveImpl(holder);
}
mPendingRemovals.clear();
// Next, move stuff
......
// Next, change stuff, to run in parallel with move animations
......
// Next, add stuff
......
}
解析:根據(jù)上面可以知道,runPendingAnimations會執(zhí)行,可看到,在這個方法中遍歷了動作列表,并讓每個Item都執(zhí)行了animatorRemoveImpl方法,其他動作的方法暫時先省略,有興趣的可以自行閱讀。
private void animateRemoveImpl(final ViewHolder holder) {
final View view = holder.itemView;
final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view);
mRemoveAnimations.add(holder);
animation.setDuration(getRemoveDuration())
.alpha(0).setListener(new VpaListenerAdapter() {
@Override
public void onAnimationStart(View view) {
dispatchRemoveStarting(holder);
}
@Override
public void onAnimationEnd(View view) {
animation.setListener(null);
ViewCompat.setAlpha(view, 1);
dispatchRemoveFinished(holder);
mRemoveAnimations.remove(holder);
dispatchFinishedWhenDone();
}
}).start();
}
解析:可以看到animatorRemoveImpl方法中實(shí)現(xiàn)了整個動畫的具體邏輯,具體怎么做不在本文范圍中,在我們執(zhí)行了動畫之后,也就是在動畫的Listener中的onAnimatorEnd中調(diào)用了dispatchRemoveFinish,還記得這個方法嗎,它會執(zhí)行onRemoveFinish方法,onRemoveFinish方法是可以供給我們重寫的。然后把item移除動作列表。
@Override
public boolean isRunning() {
return (!mPendingAdditions.isEmpty() ||
!mPendingChanges.isEmpty() ||
!mPendingMoves.isEmpty() ||
!mPendingRemovals.isEmpty() ||
!mMoveAnimations.isEmpty() ||
!mRemoveAnimations.isEmpty() ||
!mAddAnimations.isEmpty() ||
!mChangeAnimations.isEmpty() ||
!mMovesList.isEmpty() ||
!mAdditionsList.isEmpty() ||
!mChangesList.isEmpty());
}
解析:isRunning方法其實(shí)就是根據(jù)動作列表是否為空來返回結(jié)果
還有其他一些函數(shù)可以自己閱讀源代碼。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中findViewById獲取控件返回為空問題怎么解決
這篇文章主要介紹了Android中findViewById獲取控件返回為空問題怎么解決的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-06-06
Android開發(fā)EditText實(shí)現(xiàn)密碼顯示隱藏
這篇文章主要為大家詳細(xì)介紹了Android開發(fā)EditText實(shí)現(xiàn)密碼顯示隱藏,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-08-08
Android使用Handler實(shí)現(xiàn)打地鼠游戲
這篇文章主要為大家詳細(xì)介紹了Android使用Handler實(shí)現(xiàn)打地鼠游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06
Android實(shí)現(xiàn)底部導(dǎo)航欄功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)底部導(dǎo)航欄功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-06-06
Android socket如何實(shí)現(xiàn)文件列表動態(tài)訪問
本文介紹Android socket實(shí)現(xiàn)文件列表動態(tài)訪問,訪問文件夾之后通過listview展示,并在點(diǎn)擊文件夾后進(jìn)入文件夾,獲得其內(nèi)容,有此需求的朋友可以參考下2021-06-06

