简述
本文记录的是日常 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;
}
随后在执行完工作流程后,数据就能展示出来了。