文章太长,无法发布,不得已分成了两部分,这是第二篇,上一篇可以查看链接:RecyclerView 源码深入解析——绘制流程、缓存机制、动画等
滑动加载更多数据
经过上一篇的分析,可以基本肯定 RecyclerView 在滑动加载更多数据时,会调用 fill 方法使用 mCachedViews 进行子 View 的缓存和复用,下面来验证一下:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
@Override
public boolean onTouchEvent(MotionEvent e) {
...
switch (action) {
...
case MotionEvent.ACTION_MOVE: {
...
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
...
}
return true;
}
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
...
// 滚动的事件处理由 LayoutManager 完成
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
}
if (!mItemDecorations.isEmpty()) {
invalidate();
}
if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset,
TYPE_TOUCH)) {
...
} else if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
...
}
return consumedX != 0 || consumedY != 0;
}
// 如果 Adapter 更新了,则重新走一遍 layout 流程
void consumePendingUpdateOperations() {
if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
dispatchLayout();
return;
}
if (!mAdapterHelper.hasPendingUpdates()) {
return;
}
// if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
// of the visible items is affected and if not, just ignore the change.
if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
| AdapterHelper.UpdateOp.MOVE)) {
if (!mLayoutWasDefered) {
if (hasUpdatedView()) {
dispatchLayout();
} else {
// no need to layout, clean state
mAdapterHelper.consumePostponedUpdates();
}
}
} else if (mAdapterHelper.hasPendingUpdates()) {
dispatchLayout();
}
}
}
public class LinearLayoutManager extends RecyclerView.LayoutManager implements
ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
mLayoutState.mRecycle = true;
...
// fill 在上面分析过,即循环获取子 View 直到屏幕填充完毕,或子 View 消耗完毕
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
...
return scrolled;
}
}
可以看到,就像上面的推测的那样,RecyclerView 在滑动过程中会调用 fill 方法,使用 mCachedViews 进行子 View 的缓存和复用,如果 Adapter 更新了,则会重新走了一遍 layout 的流程。
Adapter
上面可以说已经把 RecyclerView最关键的部分都分析完成了,但还有一些问题是没有解决的,即 RecyclerView 是如何得知 Adapter 更新的?下面开始分析这个问题。
从 RecyclerView 的 setAdapter 方法开始看起:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();
Adapter mAdapter;
AdapterHelper mAdapterHelper;
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
...
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
...
}
void processDataSetCompletelyChanged(boolean dispatchItemsChanged) {
mDispatchItemsChangedEvent |= dispatchItemsChanged;
// 标记 DataSet 改变了,之后会调用 dispatchLayout 走一遍 layout 流程
mDataSetHasChangedAfterLayout = true;
markKnownViewsInvalid();
}
public abstract static class Adapter<VH extends ViewHolder> {
// 使用了观察者模式
private final AdapterDataObservable mObservable = new AdapterDataObservable();
@NonNull
public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType);
public abstract void onBindViewHolder(@NonNull VH holder, int position);
...
// 注册观察者
public void registerAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.registerObserver(observer);
}
public void unregisterAdapterDataObserver(@NonNull AdapterDataObserver observer) {
mObservable.unregisterObserver(observer);
}
// 通知观察者,数据发生了改变
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
public final void notifyItemInserted(int position) {
mObservable.notifyItemRangeInserted(position, 1);
}
...
}
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
// 通知所有观察者
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
public void notifyItemRangeInserted(int positionStart, int itemCount) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeInserted(positionStart, itemCount);
}
}
...
}
private class RecyclerViewDataObserver extends AdapterDataObserver {
RecyclerViewDataObserver() {
}
@Override
public void onChanged() {
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
// 如果设置了 mHasFixedSize,那么可能会省略一些工作
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
// 否则需要重新走一遍 View 的三大流程
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}
final Runnable mUpdateChildViewsRunnable = new Runnable() {
@Override
public void run() {
if (!mFirstLayoutComplete || isLayoutRequested()) {
// a layout request will happen, we should not do layout here.
return;
}
if (!mIsAttached) {
requestLayout();
// if we are not attached yet, mark us as requiring layout and skip
return;
}
if (mLayoutFrozen) {
mLayoutWasDefered = true;
return; //we'll process updates when ice age ends.
}
consumePendingUpdateOperations();
}
};
void consumePendingUpdateOperations() {
// 重新执行一遍 dispatchLayoutStep1、dispatchLayoutStep2、dispatchLayoutStep3
if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
dispatchLayout();
return;
}
if (!mAdapterHelper.hasPendingUpdates()) {
return;
}
// if it is only an item change (no add-remove-notifyDataSetChanged) we can check if any
// of the visible items is affected and if not, just ignore the change.
if (mAdapterHelper.hasAnyUpdateTypes(AdapterHelper.UpdateOp.UPDATE) && !mAdapterHelper
.hasAnyUpdateTypes(AdapterHelper.UpdateOp.ADD | AdapterHelper.UpdateOp.REMOVE
| AdapterHelper.UpdateOp.MOVE)) { // 局部更新
// 最终调用了 AdapterHelper 的 Callback 方法,Callback 的实现可以查看 RecyclerView 的 initAdapterManager 方法
mAdapterHelper.preProcess();
if (!mLayoutWasDefered) {
if (hasUpdatedView()) {
dispatchLayout();
} else {
// no need to layout, clean state
mAdapterHelper.consumePostponedUpdates();
}
}
} else if (mAdapterHelper.hasPendingUpdates()) {
dispatchLayout();
}
}
}
根据上面的分析,可以知道:
- RecyclerView 和 Adapter 之间是观察者、被观察者的关系,当 Adapter 调用了 notifyDataSetChanged 等方法时,RecyclerView 能够得知这个变化,并执行 layout 等相应的行为
- 如果 ReccylerView 设置了 mHasFixedSize,那么可以在 Adapter 被修改时执行 RecyclerView 的局部更新,从而避免重新走一遍三大流程,提高效率
总结
RecyclerView 的 measure 和 layout 分为三步:
- dispatchLayoutStep1,负责 Adpater 更新、计算并保存子 View 和动画的相关信息、预布局(如果有必要)
- dispatchLayoutStep2,负责实际上的布局,具体工作是交给 LayoutManager 完成的,但基本流程都一样,都有相同的子 View 复用机制
- dispatchLayoutStep3,负责执行动画及清理工作,默认的动画执行者是 DefaultItemAnimator
对比 RecyclerView 和 ListView:
- 同样在第一次 layout 时回调 Adapter 的相关方法从 xml 资源中加载子 View
- 同样会在第二第三次 layout 的先 remove / detach 所有的子 View,最后再 add / attach 回来,这是为了避免重复加载相同的子 View
- 同样只加载一个手机屏幕能显示的子 View
- 同样使用多个缓存数组/列表,且不同的缓存数组/列表对应的功能不同,RecylerView 的 mAttachedScrap 对应 ListView 的 mActiveViews,mCahcedViews 对应 mScrapViews
- 同样使用了观察者模式,在 Adapter 调用了 notifyDataSetChanged 等方法时,能够做出相应的改变
区别在于:
- 除了 attach / detach 之外,RecyclerView 还有可能会 remove 子 View 再 add 回来,ListView 只会 detach 再 attach
- RecyclerView 的子 View 缓存列表分工更细,共有 5 级缓存,即 mChangedScrap、mAttachedScrap、mCahcedViews、mViewCacheExtension、RecycledViewPool,其中 mViewCacheExtension 用于提供给用户自定义缓存逻辑,而 RecycledViewPool 甚至可以提供给多个 RecyclerView 共用;ListView 则只分为 mActiveViews 和 mScrapViews 两个级别
- RecyclerView 的缓存单位是 ViewHolder,ListView 的缓存单位是 View,ListView 需要配合 setTag 方法才能实现复用
网友评论