关于Adapter进行数据更新的分析

调用notifyDataSetChanged

这是一个全局更新:

  1. 他会将所有的ViewHolderflag设为ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_INVALID,
  2. 他会将所有的View.LayoutParamsmInstesDirty设置为true,代表这些数据都脏了.
  3. 将所有的Recycler.mCachedViews缓存的ViewHodler对应的View.LayoutParamsmInsetsDirty赋值为true.
  4. mCachedViews缓存的所有ViewHolderflag赋值为ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_INVALID
  5. 如果mAdapter.mHasStableIds等于false(默认都是false,除非你自己去改了),还会移除mCachedViews所有的缓存数据,移除的数据会被添加到RecycledViewPool里面.

上述所有事情做完会调用requestLayout重新开始布局.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Adapter{
void notifyDataSetChanged(){
mState.mStructureChanged = true;
mDispatchItemsChangedEvent = true;
mDataSetHasChangedAfterLayout = true;
//遍历所有的viewholder,将他们的flag设置为update和invalid
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {
holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
}
}
----还有很多代码----
----具体代码详见Adapter.processDataSetCompletelyChanged----
}
}

这里有一点很重要,在执行notifyDataSetChanged方法的时候,会去修改成员变量

  • mState.mStructureChanged
  • mDispatchItemChangedEvent
  • mDataSetHasChangedAfterLayout
    这几个参数在执行layout的时候很重要,这几个值也只有在执行方法notifyDataSetChanged的时候才会被赋值为true. 当mDataSetHasChangedAfterLayouttrue的时候,recycleview将不会执行简单动画和预加载动画.

notifyDataSetChangednotifyItemChanged区别就是:

  • notifyDataSetChanged会立马将所有的viewHolder设置为更新状态和无效状态,并且删除了所有mCachedViews数组里面的数据
  • notifyItemChanged会设置mAdapterUpdateDuringMeasure为true.然后在onMeasure阶段将变化的viewHolder设置为更新或者移除或者无用等等状态.实现原理就是会将操作保存到mPendingUpdates数组里面,然后在onMeasure阶段遍历数组进行对ViewHolder的flag修改.
  • notifyDataSetChanged不会有动画效果
  • notifyItemChanged会有动画效果

在调用全局更新的时候不会用到AdapterHelper.mPendingUpdates数组.
现在继续看调用notifyDataSetChanged时触发的requestLayout.
进入onMeasure方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Recycleview{
void onMeasure(int widthSpec,int heightSpec){
//这个参数在调用notifyDataSetChanged()时是false
//调用其他的更新方法比如notifyItemChanged(int position)的时候会被赋值为true
if(mAdapterUpdateDuringMeasure){
//所以这里为false不会进来
//我就不放代码了
}else{
//会进这里
mLayout.onMeasure(mRecycler,mState,widthSpec,heightSpec)
}
}
}

关于notifyDatasetChanged触发的onMeasure没有什么可以讲的,重点的是在通过notifyItemChanged(int position)触发的重新测量,这个下面讲,很重要.涉及到了AdapterHelper.mPendingUpdates数组的使用.

进入onLayout方法.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Recycleview{
void onLayout(boolean changed,int l,int t,int r,int b){
dispatchLayout()
}
void dispatchLayout(){
dispatchLayout1()
dispatchLayout2()
dispatchLayout3()
}
void dispatchLayout1(){
processAdapterUpdatesAndSetAnimationFlags();
mState.mInPreLayout = mState.mRunPredictiveAnimations;
if(mState.mRunSimpleAnimations){

}
if(mState.mRunPredictiveAnimations){
mLayout.onLayoutChildren(mRecycler,mState)
}
mState.mLayoutStep = State.STEP_LAYOUT;
}
void processAdaterUpdatesAndSetAnimationFlags(){
if(mDataSetHasChangedAfterLayout){
//这里是一个重点,只有调用notifyDataSetChanged()方法的时候这个值才会被设置为true,调用notifyItemChanged()时这个值不会被修改为true.
mAdapterHelper.reset();//清空mPendingUpdates和mPostponedList数组的数据
if(mDispatchItemChangedEvent(){
//是否告诉Layout数据发生了改变,意思也就是开发者可以监听onItemsChanged方法来知道数据发生了改变
mLayout.onItemsChanged(this);
}
}
//Recycleview是否设置了动画
//由于这里走的是notifyDataSetChanged这条线,上面执行了reset操作,导致这里的mAdapterHelper的数组是空的
if(predictiveItemAnimationsEnabled()){
//这方法其实就是在遍历AdapterHelper.mPendingUpdates数组,给viewHolder设置对应的flag值.
mAdapterHelper.preProcess();
}else{
//遍历AdapetHelper.mPendingUpdates数组,给对应的viewHolder设置对应的flag
mAdapterHelper.consumeUpdatesInOnePass();
}
//由于走的是全局更新所以mItemsAddedOrRemoved为false mItemsChanged也为false
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
//mFirstLayoutComplete这个值只会在第一次完整的布局结束后才会赋值为true
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout//这个值我们看到了.他是在notifyDataSetChanged方法里面被赋值为了true
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}
}

由于这里探究的是notifyDataSetChanged方法,也就没有继续探究mAdapterHelper.preprocess和mAdapterHepler.consumeUpdatesInOnepass方法的必要了.因为进去了也是空的数组.为什么空,上面已经分析了.

上面用了很大的篇幅介绍了:processAdapterUpdatesAndSetAnimationFlags()方法,这个方法很重要:

  1. 他遍历了数组mPendingUpdates也就是我们在执行更新操作notify时候存入的事件,遍历完的结果就是给对应的ViewHolder设置对应的Flag信息,并且这个时候我们也能在LayoutManager监听到ViewHolder的变化.
  2. 判断当前的Layout需不需要动画.需要动画的话会给mState.mRunSimpleAnimationsmState.mRunPredictiveAnimations赋值为true

分析完了dispatchLayout1()方法,你会发现这个方法就做了一件事情,预布局,全部都是为了动画.如果你没有动画的需求,dispatchLayout1()直接可以跳过了.并且在调用notifyDataSetChanged方法的情况下我们的dispatchLayout1()方法也没干什么事情,因为全局更新不需要动画.

这里探究过头了,dispatchLayout1 displayLayout2 displayLayout3不是我这里需要探究的.我现在需要探究的是,在调用notifyDataSetChanged的时候,他将所有的ViewHolderflag值设置为了ViewHolder.FLAG_UPDATE|ViewHolder.FLAG_INVALID,现在我需要找到他用的地方.
进入LinearLayoutManageronLayoutChildren()

1
2
3
4
5
class LinearLayoutManager{
void onLayoutChildren(){
detachAndScrapAttachedViews();
}
}

第一个用到的地方就在detachAndScrapAttachedViews().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//情况1
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
//情况2
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}

上面是原代码,下面我进行精简:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void scrapOrRecycleView(){
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
Recycleview.removeViewAt(index);
if(!holder.hasAnyOfTheFlags(INVALID | REMOVED | UPDATE)){
//只要不符合上面的任何一个情况,当前的ViewHolder都可以被缓存到mCachedView数组里面.
}else{
addViewHolderToRecycledViewPool();
}
} else {
Recycleview.detachViewFromParent(index)
if(holder.hasAnyOfTheFlags(REMOVED | !UPDATE){
mAttachedScap.add(holder)
}else{
mChangedScrap.add(holder)
}
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}

看到了精简的你会发现,只有当不是INVALIDE REMOVE UPDATE是可以被放进mCachedViews.否则当是REMOVE INVALID会被放进mAttachedScrap里面.如果是UPDATE会被放进mChangedScrap

调用notifyItemChanged(int position)

这是专门更新某一个ViewHolder.
这么一个更新操作会被保存在AdapterHelper.mPendingUpdates数组里面:

1
2
3
4
5
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
return mPendingUpdates.size() == 1;
}

构建了操作对象UpdateOp操作名字是UPDATE.关于操作有以下四种,对应着不同的更新方式:

  1. ADD
  2. REMOV
  3. UPDATE
  4. MOVE

这个操作被记录在mPendingUpdates数组里面.然后设置了Recycleview.mAdapterUpdateDuringMeasuretrue.关于这个数组什么时候会被用到后面讲.


关于Recycleview动画实现原理

ViewHolder的状态有以下几种,这也是动画的几种区别:

  1. PERSISTENT : 针对布局前和布局后都在手机界面上的View所做的动画.
  2. REMOVED : 在布局前对用户可见,但是数据已经从数据源中删除.
  3. ADDED : 新增数据到数据源,并且在布局后对用户可见.
  4. DISAPPEARING : 数据一直都存在于数据原中,但是布局后从可见变成了不可见.
  5. APPERAING : 数据一直在数据源中,但是布局后从不可见变成了可见.