注意:本文是基于 androidx.RecyclerView 1.3.2 版本的源码分析。默认使用 DefaultItemAnimator,如果使用了其他的 ItemAnimator,可能会有不同的表现。
效果图:
示例代码如下:
binding.btnNotifyItemChanged.setOnClickListener {
//对position=1的item调用 notifyItemChanged
rv.adapter?.notifyItemChanged(1)
}
在开始之前提两个问题:
1.RecyclerView 怎么 去掉 notifyItemChanged 时候的闪一下的动画?
2.如上代码所示,position=1的位置上,在 notifyItemChanged 之前和之后,使用的同一个ViewHolder 吗?
adapter 的 notifyItemChanged 方法会调用
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
RecyclerView.AdapterDataObservable 的 notifyItemRangeChanged 方法
public void notifyItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart, itemCount, null);
}
public void notifyItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
// since onItemRangeChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for(int i = mObservers.size() - 1; i >= 0; i--) {
//这里从后向前遍历,避免在遍历过程中,可能把observer删掉,可能会导致IndexoutException 的问题
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
RecyclerView.RecyclerViewDataObserver 的 onItemRangeChanged 方法
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
//调用 AdapterHelper 的 onItemRangeChanged 方法。 如果条件满足,触发更新
if(mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
AdapterHelper 的 onItemRangeChanged 方法
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
if(itemCount < 1) {
return false;
}
//AdapterHelper 添加一个延迟更新
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
//返回true
return mPendingUpdates.size() == 1;
}
RecyclerView.RecyclerViewDataObserver 的 triggerUpdateProcessor 方法
void triggerUpdateProcessor() {
if(POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
//注释1处
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
注释1处,调用 RecyclerView 的 requestLayout 方法。该方法导致 onMeasure 方法 、onLayout 方法被调用。在我们的例子中, RecyclerView 的 宽高是 match_parent,onMeasure 方法没有再去做测量,我们可以忽略。
RecyclerView 的 onLayout 方法内部主要调用了 dispatchLayout 方法。精简逻辑后的 dispatchLayout 方法如下:
/**
* 该方法可以看做是layoutChildren()方法的一个包装,处理由于布局造成的动画改变。动画的工作机制基于有5中不同类型的动画的假设:
* PERSISTENT: 在布局前后,items一直可见。
* REMOVED: 在布局之前items可见,在布局之后,items被移除。
* ADDED: 在布局之前items不存在,items是被添加到RecyclerView的。
* DISAPPEARING: 在布局前后items存在于数据集中,但是在布局过程中可见性由可见变为不可见。(这些items是由于其他变化的副作用而被移动到屏幕之外了)
* APPEARING: 在布局前后items存在于数据集中,但是在布局过程中可见性由不可见变为可见。(这些items是由于其他变化的副作用而被移动到屏幕之中了)
*
* 方法的大体逻辑就是计算每个item在布局前后是否存在,并推断出它们处于上述五种状态的哪一种,然后设置不同的动画。
* PERSISTENT类型的Views 通过 ItemAnimator 的 animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo) 方法执行动画
* DISAPPEARING类型的Views 通过 ItemAnimator 的 animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 方法执行动画
* APPEARING类型的Views 通过 ItemAnimator 的 animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo) 方法执行动画
* REMOVED和ADDED类型 (notifyItemChange 的时候,其实是把老的 itemView 移除了,然后新添加了一个itemView。这个过程就是REMOVED和ADDED类型 )的Views 通过 ItemAnimator 的 animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo) 执行动画。
*/
void dispatchLayout() {
//...
mState.mIsMeasuring = false;
if(mState.mLayoutStep == State.STEP_START) {
//调用dispatchLayoutStep1方法
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
//调用dispatchLayoutStep2方法
dispatchLayoutStep2();
} else if(mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
//...
} else {
//...
}
//调用dispatchLayoutStep3方法。
dispatchLayoutStep3();
}
预布局阶段 dispatchLayoutStep1
/**
* layout的第一步,在这个步骤会执行以下操作;
* - 处理适配器更新
* - 决定要运行哪种动画
* - 保存当前views的信息
* - 如果必要的话,运行预布局(layout)并保存相应的信息
*/
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
//注释0处,处理动画标志位
processAdapterUpdatesAndSetAnimationFlags();
//...
if(mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for(int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if(holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
//注释1处,记录动画之前的所有ViewHolder信息 animationInfo,保存到 mViewInfoStore 中
// 动画信息包括 itemView 的top、left、right、bottom 等
final ItemHolderInfo animationInfo = mItemAnimator