RecylerView 继承自ViewGroup, 是一个容器类, 自定义一个容器类, 需要实现的方法:
1. 构造方法:
2.onMeasure()
3.onLayout()
4.onIntercepterTouchEvent()
5.onTouchEvent()
源码版本: 27.1.1
===========================================================================
RecylerView.java:
@Override
L:3194 protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
...
//调用该方法,执行pre_layout, 然后获取子view 的尺寸
dispatchLayoutStep2();
}
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount(); //调用了Adapter 的getItemCount()
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState); //执行预先布局,该方法由LayoutManager实现
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
public void onLayoutChildren(Recycler recycler, State state) { L:7909
//需要在LayoutManager中实现该方法, 本次分析以LinearLayoutManager分析
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
LinearLayoutManager.java;
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm: 布局算法
// 1) by checking children and other variables, find an anchor coordinate and an anchor 寻找锚点
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
...
fill(recycler, mLayoutState, state, false); //关键代码,获取ViewHolder
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
...
//recyclerview 剩余空间
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
//循环遍历,填满RecyclerView
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
...
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler); //从缓存中获取ViewHolder, 如果没有,就创建ViewHolder
...
}
LayoutState.java:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition); //从Recyler.java 中获取View
mCurrentPosition += mItemDirection;
return view;
}
Recyler.java:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
//尝试获得指定位置的ViewHolder,要么从scrap,cache,RecycledViewPool中获取,要么直接重新创建
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there 从changed scrap集合中获取ViewHolder
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find by position from scrap/hidden list/cache 通过position从attach scrap或一级回收缓存中获取ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); ... }
// 2) Find from scrap/cache via stable ids, if exists
if (holder == null) {
...
final int type = mAdapter.getItemViewType(offsetPosition); //调用Adapter.getItemViewType()
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrap = true;
}
}
...
if (holder == null) { // fallback to recycler
// try recycler.
// Head to the shared pool.
if (DEBUG) {
Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
+ "pool");
}
holder = getRecycledViewPool().getRecycledView(type); //根据type 从RecycledViewPool中获取对应类型的viewHolder
...
if (holder == null) { //如果缓存池中没有对应类型的ViewHolder,就调用Adapter.createViewHolder()创建
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (DEBUG) {
Log.d(TAG, "getViewForPosition created new ViewHolder");
}
}
...
//获得ViewHolder后,调用bindViewHolder绑定视图数据bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition, int position, long deadlineNs) { ....
mAdapter.bindViewHolder(holder, offsetPosition); //调用了Adapter.bindViewHolder()
public final void bindViewHolder(@NonNull VH holder, int position) {
holder.mPosition = position;
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
holder.setFlags(ViewHolder.FLAG_BOUND,
ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
TraceCompat.beginSection(TRACE_BIND_VIEW_TAG);
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); 调用了Adapter.onBindViewHolder()
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
TraceCompat.endSection();
}
至此,Adapter 的各个方法调用流程已经完成.
接下来执行onLayout()
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
这里的代码就比较好理解了,并且上面提到的问题也就迎刃而解了,当我们给RecyclerView设置固定的宽高的时候,onMeasure是直接跳过了执行,那么为什么子View仍然能绘制出来。
这里可以看到,如果onMeasure没有执行,mState.mLayoutStep == State.STEP_START就成立,所以仍然会执行 dispatchLayoutStep1(), dispatchLayoutStep2();也就对应的会绘制子View。
而后面的注释也比较清楚,由于我们在Layout的时候改变了宽高,也会导致dispatchLayoutStep2();,也就是子View的重新绘制。
如果上面情况都没有,那么onLayout的作用就仅仅是dispatchLayoutStep3(),而 dispatchLayoutStep3()方法的作用除了重置一些参数,外还和执行动画有关
1.RecyclerView是将绘制流程交给LayoutManager处理,如果没有设置不会测量子View。
2.绘制流程是区分正向绘制和倒置绘制。
3.绘制是先确定锚点,然后向上绘制,向下绘制,fill()至少会执行两次,如果绘制完还有剩余空间,则会再执行一次fill()方法。
4.LayoutManager获得View是从RecyclerView中的Recycler.next()方法获得,涉及到RecyclerView的缓存策略,如果缓存没有拿到,则走我们自己重写的onCreateView方法。
5.如果RecyclerView宽高没有写死,onMeasure就会执行完子View的measure和Layout方法,onLayout仅仅是重置一些参数,如果写死,子View的measure和layout会延后到onLayout中执行。
RecycledViewPool.java:
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType); //根据type 从SparseArray<ScrapData> mScrap 中获取
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
//===========================以下来源于网络=======================
- 在
RecyclerView
中,并不是每次绘制表项,都会重新创建ViewHolder
对象,也不是每次都会重新绑定ViewHolder
数据。 RecyclerView
通过Recycler
获得下一个待绘制表项。Recycler
有4个层次用于缓存ViewHolder
对象,优先级从高到底依次为ArrayList<ViewHolder> mAttachedScrap
、ArrayList<ViewHolder> mCachedViews
、ViewCacheExtension mViewCacheExtension
、RecycledViewPool mRecyclerPool
。如果四层缓存都未命中,则重新创建并绑定ViewHolder
对象RecycledViewPool
对ViewHolder
按viewType
分类存储(通过SparseArray
),同类ViewHolder
存储在默认大小为5的ArrayList
中- 从
mRecyclerPool
中复用的ViewHolder
需要重新绑定数据,从mAttachedScrap
中复用的ViewHolder
不需要重新创建也不需要重新绑定数据。 - 从
mRecyclerPool
中复用的ViewHolder
,只能复用于viewType
相同的表项,从mCachedViews
中复用的ViewHolder
,只能复用于指定位置的表项。
=============================================华丽的分割线=========================================
RecyclerView 相关文章记录:
RecyclerView缓存机制 :https://juejin.im/post/5c696ba9e51d457f136d24ff 作者: Taylor
RecyclerView库中的遗珠 :https://blog.csdn.net/qq_17766199/article/details/83147483 作者: 唯鹿
记一次失败的RecycleView滑动定位 :https://www.jianshu.com/p/7734ae5f149c 作者: 点先生在这
自定义LayoutManager实现商品属性界面: https://github.com/xiangcman/LayoutManager-FlowLayout
自定义LayoutManager实现卡片效果: https://github.com/DingMouRen/LayoutManagerGroup
RecylerView优化:
降低item的布局层次(使用ConstraintLayout)
去除冗余的setitemclick事件:
将setitemclick事件的绑定和viewholder对应的rootview进行绑定,viewholer由于缓存机制的存在它创建的个数是一定的,所以和它绑定的setitemclick对象也是一定的。
还有另一种做法可以通过rv自带的addOnItemTouchListener来实现点击事件,原理就是rv在触摸事件中会使用到addOnItemTouchListener中设置的对象,然后配合GestureDetectorCompat实现点击item
复用pool缓存:
这个复用pool是针对item中包含rv的情况才适用,如果rv中的item都是普通的布局就不需要复用pool
保存嵌套rv的滑动状态:
linearlayoutmanager中有对应的onSaveInstanceState和onRestoreInstanceState方法来分别处理保存状态和恢复状态,它的机制其实和activity的状态恢复非常类似,我们需要做的就是当rv被移除屏幕调用onSaveInstanceState,移回来时调用onRestoreInstanceState即可。
视情况设置itemanimator动画:
关闭的方法直接调用setItemAnimator(null)即可
diffutil一个神奇的工具类:
弊端一:
看示例代码应该也能察觉到,要想使用diffutil必须准备两个数据集,这就是一个比较蛋疼的事情.
原先我们只需要维护一个数据集就可以,现在就需要我们同时维护两个数据集,两个数据集都需要有一份自己的数据.
如果只是简单将数据从一个集合copy到另一个集合是可能会导致问题的,会涉及到对象的深拷贝和浅拷贝问题,你必须保证两份数据集都有各自独立的内存,否则当你修改其中一个数据集可能会造成另一个数据集同时被修改掉的情况。
弊端二:
为了实现callback接口必须实现四个方法,其中areContentsTheSame是最难实现的一个方法,因为这里涉及到对比同type的item内容是否一致,这就需要将该item对应的数据bean进行比较,怎么比较效率会高点,目前能想到的方法就是将bean转换成string通过调用equals方法进行比较,如果item的数据bean对应的成员变量很少如示例所示那倒还好,这也是网上很多推荐diffutil文章避开的问题。
但是如果bean对应的成员很多,或者成员变量含有list,里面又包含各种对象元素,想想就知道areContentsTheSame很难去实现,为了引入一个diffutil额外增加这么多的逻辑判断有点得不偿失。
弊端三:
diffutil看起来让人捉摸不透的item动画行为,以上面代码为例
setHasFixedSize:
swapadapter:
getAdapterPosition和getLayoutPosition:
大部分情况下调用这两个方法得到的结果是一致的,都是为了获得holder对应的position位置,但getAdapterPosition获取位置更为及时,而getLayoutPosition会滞后到下一帧才能得到正确的position,如果你想及时得到holder对应的position信息建议使用前者。
removeview和detachview:
这两个方法在rv进行排布item的时候会遇到,removeview就是大家很常见的操作,但是detachview就不太常见了,其实removeview是一个更为彻底的移除view操作,内部是会调用到detachview的,并且会调用到我们很熟悉的ondetachfromwindow方法,而detachview是一个轻量级的操作,内部操作就是简单的将该view从父view中移除掉,rv内部调用detachview的场景就是对应被移除的view可能在近期还会被使用到所以采用轻量级的移除操作,removeview一般都预示着这个holder已经彻底从屏幕消失不可见了