RecyclerView 源码初探

简述

本文记录的是日常 RecyclerView 使用的原理分析,想从平常使用的几个方法入手,看看 RecyclerView 是如何能实现数据展示的,同时也明确下适配器各实现方法都在什么时候调用。


相关类

LayoutManager 是个抽象类,写在了 RecyclerView 一起,作用是测量和定位子布局,同时也决定着不可见时的回收策略。

Recycler 是个 final 内部类,作用是销毁或解绑子布局用于复用。

RecycledViewPool 是个静态类,写在 RecyclerView 一起,可以使 Views 在多个 RecyclerView 之间共享。

ChildHelper 协助类,应该是协助管理子布局。

public void setLayoutManager(@Nullable LayoutManager layout) {
    //这个方法里做了这么几件事
    //1.停止滚动,准备要赋值了
    //2.Recycler 清场
    //3.ChildHelper 清场
    //4.赋值 LayoutManager
    //5.Recycler 更新 View 缓存
    //6.最后的 requestLayout() 感觉没做什么
}
public void setAdapter(@Nullable Adapter adapter) {
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    processDataSetCompletelyChanged(false);
    requestLayout();
}
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,boolean removeAndRecycleViews) {
    if (!compatibleWithPrevious || removeAndRecycleViews) {
        //这步跟前面的 清场 操作差不多
        removeAndRecycleViews();
    }
    mAdapterHelper.reset();
    final Adapter oldAdapter = mAdapter;
    //adapter 赋值
    mAdapter = adapter;
    if (adapter != null) {
        //注册观察者,adapter 是被观察者,它的变动会通知观察者
        adapter.registerAdapterDataObserver(mObserver);
        adapter.onAttachedToRecyclerView(this);
    }
    //因为 adapter 的改变,LayoutManager, Recycler, 还有 RecyclerViewPool 都要通知到变更
    if (mLayout != null) {
        mLayout.onAdapterChanged(oldAdapter, mAdapter);
    }
    mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
}

前面说到的观察者是 RecyclerViewDataObserver 是 RecyclerView 的内部类,继承自 AdapterDataObserver。定义了 6 个方法,应该都会在被观察者变更时,按具体情况回调到。

onChanged();
onItemRangeChanged(int positionStart, int itemCount);
//这个内部其实调了 onItemRangeChanged(int positionStart, int itemCount)
onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload);
onItemRangeInserted(int positionStart, int itemCount);
onItemRangeRemoved(int positionStart, int itemCount);
onItemRangeMoved(int positionStart, int itemCount);

Adapter 看似被观察,其实其内容拥有一个 AdapterDataObservable 类型的变量 mObservable,正是它进行的注册观察者。AdapterDataObservable 继承自 Observable。我们知道被观察者会持有一组观察者对象引用,这也正是系统提供的观察者模式实现代码。那么整个流程怎么走?或者说被观察者的一举一动如何让观察者感知到的呢?

试想一下,我们在用 RecyclerView 实现功能时,是需要继承自 Adapter 实现子类 Adapter 的,并且还要实现几个抽象方法。

public class SimpleAdapter extends RecyclerView.Adapter {
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {}
    
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {}
    
    public int getItemCount(){}
}

关键的触发点是 notifyDataSetChanged() 方法,在给 SimpleAdapter 数据集合变量赋值后,需要调用该方法使得列表刷新重绘。该方法最终调用的就是观察者的 onChanged() 方法,而在 onChanged() 方法里关键的就是调用了 requestLayout() 方法。

由于还不了解 requestLayout() 方法的逻辑,我打算换个思路,从 Activity 的启动说起。

从启动含有 RecyclerView 控件的 Activity 说起,我们知道 View 的工作流程会在执行完 onResume() 方法之后开始(也就是 measure, layout, draw)的过程,而上述分析的 setLayoutManager()setAdapter() 方法一般会在 onCreate() 方法里调用。RecyclerView 作为一个类似容器的布局控件应该需要关心自身的工作流程,

protected void onMeasure(int widthSpec, int heightSpec) {
    //mLayout 显然是有值的,假设它是 LinearLayoutManager 类型
    if (mLayout.isAutoMeasureEnabled()) {
        //LinearLayoutManager 的 onMeasure() 方法又调用了 RecyclerView 的 defaultOnMeasure()
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        dispatchLayoutStep2();
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    }
}
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    dispatchLayout();
    mFirstLayoutComplete = true;
}
public void onDraw(Canvas c) {
    super.onDraw(c);
    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
        mItemDecorations.get(i).onDraw(c, this, mState);
    }
}

最终还是要看下 requestLayout() 方法,看来是绕不过去的,于是我搜了这篇文章帮助理解 Android View 深度分析requestLayout、invalidate与postInvalidate 。

简单来说就是,无论哪个 View 发起了 requestLayout,都会根据视图树的结构最后交给 ViewRootImpl 对象重新开始整体的 View 工作流程,来完成重新测量,布局,绘制。

所以对于 RecyclerView,重点还是在测量和布局的过程,因为绘制只涉及到 Item 装饰器,可以先忽略。我们的目标是看看自定义 Adapter 那几个重写方法分别在什么时候调用以及又是怎么变更数据的。

private void dispatchLayoutStep1() {
    mState.assertLayoutStep(State.STEP_START);
    //这里会触发一次获取 item 数量
    mState.mItemCount = mAdapter.getItemCount();
    mState.mLayoutStep = State.STEP_LAYOUT;
}
private void dispatchLayoutStep2() {
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    //这里也会触发一次获取 item 数量
    mState.mItemCount = mAdapter.getItemCount();
    //这步很关键,要开始 item 布局了
    mLayout.onLayoutChildren(mRecycler, mState);
    mState.mLayoutStep = State.STEP_ANIMATIONS;
}

上面两步操作在 onMeasure 或者 onLayout 过程会执行,由此知道 getItemCount() 方法会先调用。

//以 LinearLayout 为例
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    //这里面代码逻辑很多,我们就看关键
    fill(recycler, mLayoutState, state, false);
}
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
        //也只看关键方法
        layoutChunk(recycler, state, layoutState, layoutChunkResult);
    }
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
    //这句是关键,表示依次获取 Item 的 View
    View view = layoutState.next(recycler);
    RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
            addView(view);
        } else {
            addView(view, 0);
        }
    } else {
        if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) {
            addDisappearingView(view);
        } else {
            addDisappearingView(view, 0);
        }
    }
    measureChildWithMargins(view, 0, 0);
}
//接下去实际上是 LinearLayoutManager 的相关的静态类 LayoutState 里的调用
//不过通过 next 方法又回到了 Recycler 
View next(RecyclerView.Recycler recycler) {
    final View view = recycler.getViewForPosition(mCurrentPosition);
    //mItemDirection 表示一个方向,往头部的话就是 -1,往尾部的话就是 1.
    mCurrentPosition += mItemDirection;
    return view;
}
//Recycler
//根据 position 获取 View,这个 View 对象在这里有可能是新建,有可能是复用之前的,要看对应数据有没有改变
public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
//根据 position 获取 ViewHolder,这个 ViewHolder 对象的创建可以是从 Recycler scrap, cache,
//RecycledViewPool 或者新建
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
    if (position < 0 || position >= mState.getItemCount()) {
        //这个异常应该大家比较熟悉了,通常引起这个问题的现象是,数据集合变更个数后没有通知刷新,导致展示与数据不一致
        throw new IndexOutOfBoundsException("Invalid item position " + position
                  + "(" + position + "). Item count:" + mState.getItemCount()
                  + exceptionLabel());
    }
    ViewHolder holder = null;
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        //这个方法在 RecyclerView 需要展示多种布局样式时,用来做类型区分
        final int type = mAdapter.getItemViewType(offsetPosition);
        //前面其实有很多获取 holder 对象的方式,应该就是方法注释里说的,从 Recycler scrap, cache 等
        //最后没办法了,就要创建了
        if (holder == null) {
            //还是看关键部分,这个就有点接近 onCreateViewHolder() 方法了
            //其实这方法里就是调了 onCreateViewHolder() 来返回 holder 对象
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
    }
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        //这里关联数据
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    return holder;
}
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) {
    holder.mOwnerRecyclerView = RecyclerView.this;
    final int viewType = holder.getItemViewType();
    //这个也是关键代码,马上就能看到 onBindViewHolder() 了
    //该方法再往下转两层就调用了 onBindViewHolder()
    mAdapter.bindViewHolder(holder, offsetPosition);
    return true;
}

随后在执行完工作流程后,数据就能展示出来了。

你可能感兴趣的:(RecyclerView 源码初探)