文章篇幅较长,文末有总结和流程图。
个人主页:https://chengang.plus/
1、用法
一个比较简单的用法如下:
class AndroidDeepLearnActivity : Activity() {
lateinit var dlRecyclerView: RecyclerView
lateinit var context: Context
private val images = arrayOf(
"http://api.learn2crack.com/android/images/donut.png",
"http://api.learn2crack.com/android/images/eclair.png",
"http://api.learn2crack.com/android/images/froyo.png",
"http://api.learn2crack.com/android/images/ginger.png",
"http://api.learn2crack.com/android/images/honey.png",
"http://api.learn2crack.com/android/images/icecream.png",
"http://api.learn2crack.com/android/images/jellybean.png",
"http://api.learn2crack.com/android/images/kitkat.png",
"http://api.learn2crack.com/android/images/lollipop.png",
"http://api.learn2crack.com/android/images/marshmallow.png"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_android_deep_learn)
context = this
dlRecyclerView = findViewById(R.id.dp_recycler_view)
val itemDecorator = DividerItemDecoration(context, DividerItemDecoration.VERTICAL)
itemDecorator.setDrawable(context.getDrawable(R.drawable.divider))
dlRecyclerView.addItemDecoration(itemDecorator)
val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
dlRecyclerView.layoutManager = layoutManager
dlRecyclerView.adapter = DLRecyclerViewAdapter(context, images)
}
class DLRecyclerViewAdapter(context: Context, images: Array) :
RecyclerView.Adapter() {
private val innerImages = images
private val innerCount = images?.size
private val mContext = context
private var layoutInflater: LayoutInflater
init {
layoutInflater = LayoutInflater.from(mContext)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DLViewHolder {
val view = layoutInflater.inflate(R.layout.dl_recyclerview_item_view, parent, false)
return DLViewHolder(view)
}
override fun getItemCount(): Int {
return innerCount
}
override fun onBindViewHolder(holder: DLViewHolder, position: Int) {
holder.itemImage = holder.itemView.findViewById(R.id.dl_image_item)
holder.itemImage.setImageURI(innerImages[position])
}
}
class DLViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
lateinit var itemImage: SimpleDraweeView
}
}
2、第一次构建
2.1 RecyclerView构造函数
RecyclerView在xml文件中定义,在View初始化的时候,会调用到他的构造函数:
public RecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initAdapterManager();
initChildrenHelper();
createLayoutManager(context, layoutManagerName, attrs, defStyleAttr, 0);
setNestedScrollingEnabled(nestedScrollingEnabled);
}
2.1.1 AdapterHelper
initAdapterManager方法初始化了一个AdapterHelper对象,如下:
mAdapterHelper = new AdapterHelper(new AdapterHelper.Callback() {
@Override
public ViewHolder findViewHolder(int position) {}
@Override
public void offsetPositionsForRemovingInvisible(int start, int count) {}
@Override
public void offsetPositionsForRemovingLaidOutOrNewView(int positionStart, int itemCount) {}
@Override
public void markViewHoldersUpdated(int positionStart, int itemCount, Object payload) {}
@Override
public void onDispatchFirstPass(AdapterHelper.UpdateOp op) {
dispatchUpdate(op);
}
void dispatchUpdate(AdapterHelper.UpdateOp op) {}
@Override
public void onDispatchSecondPass(AdapterHelper.UpdateOp op) {
dispatchUpdate(op);
}
@Override
public void offsetPositionsForAdd(int positionStart, int itemCount) {}
@Override
public void offsetPositionsForMove(int from, int to) {}
}
2.1.2 ChildHelper
initChildrenHelper方法初始化了一个ChildHelper对象,如下:
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
@Override
public int getChildCount() {
return RecyclerView.this.getChildCount();
}
@Override
public void addView(View child, int index) {
RecyclerView.this.addView(child, index);
}
@Override
public int indexOfChild(View view) {
return RecyclerView.this.indexOfChild(view);
}
@Override
public void removeViewAt(int index) {
final View child = RecyclerView.this.getChildAt(index);
RecyclerView.this.removeViewAt(index);
}
@Override
public View getChildAt(int offset) {
return RecyclerView.this.getChildAt(offset);
}
@Override
public void removeAllViews() {}
@Override
public ViewHolder getChildViewHolder(View view) {
return getChildViewHolderInt(view);
}
@Override
public void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams) {}
@Override
public void detachViewFromParent(int offset) {}
@Override
public void onEnteredHiddenState(View child) {}
@Override
public void onLeftHiddenState(View child) {}
}
2.1.3 xml中定义layoutManager
如果在xml中定义了layoutManager类型,此处会调用createLayoutManager方法创建LayoutManager。
2.1.4 setNestedScrollingEnabled
对应nestedScrollingEnabled属性,既可以在xml中定义RecyclerView控件时定义,也可以在代码中设置。对应的是由父控件分发滚动消息还是自身分发,默认是自身分发。
该属性在Android SDK_INT 大于等于21的时候才有效。否则一律默认为true。
2.2 LayoutManager
以LinearLayoutManager为例,他继承自RecyclerView.LayoutManager。
2.2.1 构造函数
LinearLayoutManager
public LinearLayoutManager(Context context, @RecyclerView.Orientation int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
}
这里有三个参数,第一个是上下文;第二个是滚动方向,分为竖直和水平方向;第三个参数是视图排布方向,true的话,视图从尾到头排布,否则从头到尾排布。
2.2.2 设置方向
通过setOrientation方法设置滚动,如下:
LinearLayoutManager
public void setOrientation(@RecyclerView.Orientation int orientation) {
if (orientation != mOrientation || mOrientationHelper == null) {
mOrientationHelper = OrientationHelper.createOrientationHelper(this, orientation);
mAnchorInfo.mOrientationHelper = mOrientationHelper;
mOrientation = orientation;
requestLayout();
}
}
在这里可以看到如果不设置方向的话,默认是竖直方向。第一次进来的时候,mOrientationHelper没有初始化,为null,这里先执行createOrientationHelper方法:
OrientationHelper
public static OrientationHelper createOrientationHelper(
RecyclerView.LayoutManager layoutManager, @RecyclerView.Orientation int orientation) {
switch (orientation) {
case HORIZONTAL:
return createHorizontalHelper(layoutManager);
case VERTICAL:
return createVerticalHelper(layoutManager);
}
throw new IllegalArgumentException("invalid orientation");
}
根据不同的方向分别创建不同的Helper,以VERTICAL为例,看看createVerticalHelper方法:
OrientationHelper
public static OrientationHelper createVerticalHelper(RecyclerView.LayoutManager layoutManager) {
return new OrientationHelper(layoutManager) {};
}
这里新建一个持有RecyclerView.LayoutManager对象的OrientationHelper。
- AnchorInfo
到这里会把创建的OrientationHelper对象给到一个AnchorInfo;把用户设置的orientation给到全局变量mOrientation。
2.2.3 设置反转视图
如果初始化LinearLayoutManager的时候,将第三个参数设置为true,那么就会反转视图。将LinearLayoutManager中的全局变量mReverseLayout设置为用户传递的参数,同时执行requestLayout方法。
2.3 设置LayoutManager
贴一下setLayoutManager方法看看:
RecyclerView
public void setLayoutManager(@Nullable LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
mLayout = layout;
if (layout != null) {
if (layout.mRecyclerView != null) {
throw new IllegalArgumentException("LayoutManager " + layout
+ " is already attached to a RecyclerView:"
+ layout.mRecyclerView.exceptionLabel());
}
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
- mLayout是LayoutManager类型,如果当前的形参layout与mLayout是同一个对象,就直接返回。
- 停止滚动。
- mLayout不为空,意味着之前已经设置过了,此时就要停止动画,将mLayout里面所有的视图全部清掉,并置RecyclerView和mLayout为空,等于是重新开始了。
- Recycler,回收者,是一个全局new出来的对象,在RecyclerView中。看注释他的作用是管理着那些报废或脱离父视图的组件重用。报废的意思是视图仍然依附着父视图,只是被标记为被移除或重用。Recycler持有几个ArrayList变量,都是与item缓存回收相关。这里执行clear方法,意味着将缓存的item全部清除。
- 初始化mChildHelper,同时将mLayout重新复制,并同时设置LayoutManager中的RecyclerView变量。
- 更新缓存大小,不设置的话,默认两个。同时请求更新视图。
2.3.1 更新视图
其实在new LinearLayoutManager的时候,会调用setOrientation
和setReverseLayout
方法,这两个方法里面都用调用requestLayout,在LayoutManager中定义:
public void requestLayout() {
if (mRecyclerView != null) {
mRecyclerView.requestLayout();
}
}
但是在调用这两个方法的时候mRecyclerView没有被初始化,一直到setLayoutManager方法的时候才被初始化。
在setLayoutManager执行的最后,调用requestLayout方法,在RecyclerView中定义:
@Override
public void requestLayout() {
if (mInterceptRequestLayoutDepth == 0 && !mLayoutSuppressed) {
super.requestLayout();
} else {
mLayoutWasDefered = true;
}
}
这里会执行到View的requestLayout方法,触发视图更新。
我们知道View更新的流程是onMeasure,onLayout,onDraw.
通过查看这三个方法,我们发现不设置adapter,根本不会触发视图的更新操作。
2.4 设置adapter
最开始的demo中的DLRecyclerViewAdapter类继承自RecyclerView.Adapter,可以限定一下Adapter的ViewHolder类型,但是对于在RecyclerView中加载复杂类型的item来说,需要考虑到ViewHolder的通用性。
RecyclerView
public void setAdapter(@Nullable Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
2.4.1 setLayoutFrozen
setLayoutFrozen(false)
意味着items被enable和滚动
2.4.2 setAdapterInternal
setAdapterInternal
看看这个方法:
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
mAdapter.onDetachedFromRecyclerView(this);
}
if (!compatibleWithPrevious || removeAndRecycleViews) {
removeAndRecycleViews();
}
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
}
- 先判断mAdapter是否为空,不为空的话,解注册mObserver,是一个全局新建的对象,RecyclerViewDataObserver类型,这个类继承自AdapterDataObserver,实现了dataset changed相关的函数,比如
onItemRangeChanged
,onItemRangeInserted
等。
解注册最终实现的地方在Observable类中:
Observable
public void unregisterObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
int index = mObservers.indexOf(observer);
if (index == -1) {
throw new IllegalStateException("Observer " + observer + " was not registered.");
}
mObservers.remove(index);
}
}
mObservers是一个AdapterDataObserver类型的ArrayList。
- removeAndRecycleViews做一些清理相关的工作以及终止item动画等
- AdapterHelper是在RecyclerView的构造函数中创建的,在这里复位
- 将mAdapter赋值为新的adapter
- 重新注册mObserver
- 调用onAttachedToRecyclerView,在Adapter中可以复写这个方法
- 执行LayoutManager类的onAdapterChanged方法
- 调用Recycler的onAdapterChanged方法:
Recycler
void onAdapterChanged(Adapter oldAdapter, Adapter newAdapter, boolean compatibleWithPrevious) {
clear();
getRecycledViewPool().onAdapterChanged(oldAdapter, newAdapter, compatibleWithPrevious);
}
清理废弃和缓存的item。
2.4.3 RecycledViewPool
获取RecycledViewPool,看看这个方法:
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
首次设置Adapter,mRecyclerPool参数为空,需要new一个。这个类的作用是在多个RecycledView之间共享item,可以通过setRecycledViewPool方法设置一个RecycledViewPool,如果不设置的话,会默认创建一个。
接下来执行RecycledViewPool的onAdapterChanged方法,主要逻辑是将旧的Adapter detach,将新的Adapter attach,对应的函数操作就是将变量mAttachCount自减或自增。
回到setAdapter方法,接下来执行processDataSetCompletelyChanged方法,主要目的是将之前Holder持有的view设置为valid。
下一步执行requestLayout,更新视图。
2.5 更新视图
2.5.1 onMeasure
因为有各种变量判断,这里简单抽取一下,就只剩下几行代码:
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
startInterceptRequestLayout();
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear
}
- 触发获取item数量
- 触发RecyclerView的onMeasure,设置滚动视图的尺寸
- startInterceptRequestLayout和stopInterceptRequestLayout成对出现,目的是为了避免多个requestLayout方法同时调用,待当次执行完毕后,再执行
2.5.2 onLayout
看看这个定义在RecyclerView中的方法:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
调用dispatchLayout:
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();
}
mState是一个全局的State对象,持有RecyclerView的滚动位置和焦点等状态信息。
2.5.3 dispatchLayoutStep1
第一次到这里只会执行简单的逻辑:
RecyclerView
private void dispatchLayoutStep1() {
mState.mLayoutStep = State.STEP_LAYOUT;
}
2.5.4 dispatchLayoutStep2
接下来执行dispatchLayoutStep2函数:
RecyclerView
private void dispatchLayoutStep2() {
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
}
首先获取RecyclerView里面的item数量,这里会直接调用到我们自己继承Adapter的类中,覆写的getItemCount函数。
给State类的一些变量赋值。
调用LinearLayoutManager的onLayoutChildren方法,携带Recycler和State参数。
2.5.5 onLayoutChildren
为子item布局,看看这个方法:
LinearLayoutManager
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
final int firstLayoutDirection;
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
}
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
}
主要填充方式分两种,锚点在起始位置或底部。如果把设备当做一个Stack,那么屏幕顶部为start,底部为end。默认mLayoutFromEnd变量为false。
这里会调用两次fill方法,一个填充到底部,一个填充到顶部。
2.5.6 fill填充
LinearLayoutManager
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
}
return start - layoutState.mAvailable;
}
这个方法主要看layoutChunk方法,绘制每个view块。
2.5.7 layoutChunk 绘制view块
LinearLayoutManager
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
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);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecoratedWithMargins(view, left, top, right, bottom);
}
先调用next方,传递Recycler参数,然后addView。
2.5.8 next 遍历获取子View
LinearLayoutManager
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
2.5.9 getViewForPosition
RecyclerView.Recycler
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
2.5.10 tryGetViewHolderForPositionByDeadline
RecyclerView.Recycler
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
if(holder == null){
final int type = mAdapter.getItemViewType(offsetPosition);
}
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
}
在tryGetViewHolderForPositionByDeadline方法中,会有各种boolean参数判断,这里主要看holder,第一次创建的时候肯定是null,会直接调用createViewHolder创建Holder。
2.5.11 getItemViewType
获取view的类型,一般的,如果RecyclerView里面如果item的类型比较复杂,类似电商App首页这种,就要为banner,限时热卖,商品列表等设置不同的视图类型。
如果在定义的Adapter中不覆写这个方法,就默认返回0。
2.5.11 创建Holder
createViewHolder
方法用来创建ViewHolder。
RecyclerView.Adapter
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
return holder;
}
这里onCreateViewHolder
就直接回调到我们代码里面覆写RecyclerView.Adapter的onCreateViewHolder方法中,在这个方法中,创建自定义的ViewHolder(继承自RecyclerView.ViewHolder),然后返回这个ViewHolder的实例。
记下来继续tryGetViewHolderForPositionByDeadline方法中的流程。
2.5.12 tryBindViewHolderByDeadline
RecyclerView.Recycler
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition, int position, long deadlineNs) {
holder.mOwnerRecyclerView = RecyclerView.this;
final int viewType = holder.getItemViewType();
long startBindNs = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willBindInTime(viewType, startBindNs, deadlineNs)) {
// abort - we have a deadline we can't meet
return false;
}
mAdapter.bindViewHolder(holder, offsetPosition);
long endBindNs = getNanoTime();
mRecyclerPool.factorInBindTime(holder.getItemViewType(), endBindNs - startBindNs);
attachAccessibilityDelegateOnBind(holder);
if (mState.isPreLayout()) {
holder.mPreLayoutPosition = position;
}
return true;
}
调用tryBindViewHolderByDeadline方法,核心是bindViewHolder。
2.5.13 bindViewHolder
RecyclerView.Recycler
public final void bindViewHolder(@NonNull VH holder, int position) {
holder.mPosition = position;
holder.clearPayload();
final ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
if (layoutParams instanceof RecyclerView.LayoutParams) {
((LayoutParams) layoutParams).mInsetsDirty = true;
}
}
public void onBindViewHolder(@NonNull VH holder, int position, @NonNull List
跟onCreateViewHolder
方法类似,到这里,就直接回调到我们代码里面覆写RecyclerView.Adapter的onBindViewHolder方法中,在这个方法中,可以为itemView中的child View设置数据。
通过以上步骤之后,会调用addView将每一个获取到的child view添加到RecyclerView中。
2.5.14 onDraw 绘制
绘制代码如下:
RecyclerView
@Override
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);
}
}
这里的mItemDecorations是ArrayList
以DividerItemDecoration为例,这里会调用它的onDraw方法。
2.5.15 绘制item间隔
DividerItemDecoration
继承自RecyclerView.ItemDecoration
,在上一步中会直接调用到DividerItemDecoration的onDraw方法,并根据不同的方向绘制间隔,前提是DividerItemDecoration要调用setDrawable设置绘制资源。
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null || mDivider == null) {
return;
}
if (mOrientation == VERTICAL) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
2.6 总结
到这里,第一次实例化RecyclerView,并创建各种对象,回调Adapter的各个函数,并填充绘制item view,最后绘制item之间的间隔。
2.6.1 RecyclerView持有的对象
- RecyclerViewDataObserver mObserver,处理数据变动,增加或删除数据。
- Recycler mRecycler,负责item view回收复用。
- AdapterHelper mAdapterHelper,处理Adapter更新。
- ChildHelper mChildHelper,初始化时,会有一个CallBack接口,LayoutManager通过它的对象调用RecyclerView中对应的方法。
- Adapter mAdapter,完成data和view的绑定,并提供创建ViewHolder的回调方法。
- LayoutManager mLayout,管理item view的布局,回收,重用等。
3、缓存与回收
在滑动列表的过程中,会存在缓存,回收复用item view的过程。
3.1 滑动事件处理
滑动的时候,在RecyclerView里面会通过onTouchEvent方法获取滑动事件:
RecyclerView
@Override
public boolean onTouchEvent(MotionEvent e) {
switch (action) {
case MotionEvent.ACTION_DOWN: {
break;
}
case MotionEvent.ACTION_MOVE: {
if (mScrollState != SCROLL_STATE_DRAGGING) {
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
if (mScrollState == SCROLL_STATE_DRAGGING) {
if (scrollByInternal(canScrollHorizontally ? dx : 0, canScrollVertically ? dy : 0, e, TYPE_TOUCH)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
break;
}
}
return true;
}
放拖拽滑动的时候,会走到ACTION_MOVE里面,并调用scrollByInternal方法,这里的x是水平方向滑动的距离,y是竖直方向滑动的距离:
RecyclerView
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
scrollStep(x, y, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX = x - consumedX;
unconsumedY = y - consumedY;
}
}
接着执行scrollStep方法,传递x,y参数,以及一个int数组。
RecyclerView
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
int consumedX = 0;
int consumedY = 0;
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
if (consumed != null) {
consumed[0] = consumedX;
consumed[1] = consumedY;
}
}
如果设置RecyclerView为竖直方向滚动,那么dy不等于0,调用scrollVerticallyBy方法,在LinearLayoutManager类中:
LinearLayoutManager
@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 delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
mLayoutState.mRecycle = true;
updateLayoutState(layoutDirection, absDelta, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
可以看到上边两个方法的核心目的是为了计算dy值,也就是在竖直方向上滑动了多少距离。
3.1.1 更新视图状态
updateLayoutState方法里面计算了偏移的delta值:
LinearLayoutManager
private void updateLayoutState(int layoutDirection, int requiredSpace, boolean canUseExistingSpace, RecyclerView.State state) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
calculateExtraLayoutSpace(state, mReusableIntPair);
mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart;
mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd;
int scrollingOffset;
if (layoutToEnd) {
mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding();
// get the first child in the direction we are going
final View child = getChildClosestToEnd();
// the direction in which we are traversing children
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// calculate how much we can scroll without adding new children (independent of layout)
scrollingOffset = mOrientationHelper.getDecoratedEnd(child) - mOrientationHelper.getEndAfterPadding();
} else {
...
}
mLayoutState.mAvailable = requiredSpace;
if (canUseExistingSpace) {
mLayoutState.mAvailable -= scrollingOffset;
}
mLayoutState.mScrollingOffset = scrollingOffset;
}
这里可以看到有一个calculateExtraLayoutSpace方法,用来计算额外布局的空间,LinearLayoutManager为了流畅滑动考虑,提前布局额外一页的item。在这个方法中,如果是竖直方向滑动,mReusableIntPair第一个值为0,第二个值为RecyclerView高度减去top和bottom的padding。
layoutToEnd,如果向下滑动,那么这个boolean值为true。
getChildClosestToEnd,用于获取当前页最后一个child item view,具体是通过调用ChildHelper.getChildAt(index)方法,这里的index等于总的item个数减去隐藏(hidden)的个数。
getDecoratedEnd,获取的是这个view bottom值加上他所属间隔view的bottom,再加上他的margin_bottom值,至此可以得到最后一个item view的y值。
scrollingOffset最终就等于getDecoratedEnd获取的值减去RecyclerView的padding_top值。
LayoutState mLayoutState里面记录了child view的位置坐标相关信息。
3.2 fill 填充视图
填充之前会做回收工作:
LinearLayoutManager
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
recycleByLayoutState(recycler, layoutState);
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
}
}
3.2.1 开始回收
recycleByLayoutState方法里面会做回收逻辑:
LinearLayoutManager
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
int scrollingOffset = layoutState.mScrollingOffset;
int noRecycleSpace = layoutState.mNoRecycleSpace;
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset, int noRecycleSpace) {
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// stop here
recycleChildren(recycler, 0, i);
return;
}
}
}
- 如果向上滑动,那么调用recycleViewsFromStart回收顶部滑出界面的item view。
- 如果向下滑动,调用recycleViewsFromEnd回收。
- 以recycleViewsFromStart为例,如果向上滑动,就会回收滑出界面的item view。
3.2.2 remove子view
通过执行recycleChildren方法remove子view。
LinearLayoutManager
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
} else {
for (int i = startIndex; i > endIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
RecyclerView.LayoutManager
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);
recycler.recycleView(view);
}
向上滑动时,从头部开始回收,回收的策略是从RecyclerView中删除child view,在RecyclerView的initChildrenHelper方法中,有覆写ChildHelper.Callback接口的removeViewAt函数。
删除之后通过recycleView方法缓存。
3.2.3 缓存子view
缓存子view通过recycleView方法实现,在RecyclerView类中:
RecyclerView.Recycler
public void recycleView(@NonNull View view) {
ViewHolder holder = getChildViewHolderInt(view);
recycleViewHolderInternal(holder);
}
void recycleViewHolderInternal(ViewHolder holder) {
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
}
- ArrayList
mCachedViews,这个List中只能存储最多2个ViewHolder,如果大于等于2个,先删除第一个,并不是不要了,而是交给RecycledViewPool托底:
RecyclerView.Recycler
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
getRecycledViewPool().putRecycledView(holder);
}
RecyclerView.RecycledViewPool
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
SparseArray mScrap = new SparseArray<>();
static class ScrapData {
final ArrayList mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
- 加入RecycledViewPool缓存之前,会调用mAdapter.onViewRecycled(holder)通知客户端这个ViewHolder被回收缓存了,可以在自己的Adapter中覆写这个方法做对应的处理。
- ScrapData内部静态类创建了一个mScrapHeap,List类型,存储的是ViewHolder对象,而外部类定义了一个SparseArray,用于存储ScrapData,key是ViewHolder的viewType,也就是说将ViewHolder通过viewType分开存储。
- 存ViewHolder的时候,先获取viewType,并创建一个对应的ArrayList
,再将要缓存的ViewHolder加进去。 - RecycledViewPool完成缓存之后,mCachedViews删除最后一个ViewHolder。
- 接下来还是会尝试将ViewHolder缓存到mCachedViews中,如果缓存失败,还是交给RecycledViewPool。
3.2.4 创建新的子view
回到fill方法中,在缓存之后,要重新调用layoutChunk方法,去填充向下滑动产生的空间。
LinearLayoutManager
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
addView(view);
}
从next方法出发,跟第一次构建时的调用栈类似,不过这次填充会从缓存里面去取。
RecyclerView.Recycler
ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) {
if(holder == null){
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if(holder == null){
holder = getRecycledViewPool().getRecycledView(type);
}
mAdapter.bindViewHolder(holder, offsetPosition);
}
getScrapOrHiddenOrCachedHolderForPosition的策略是:
- 先从ArrayList
mAttachedScrap里面取,mAttachedScrap存储的是那些被hidden,但是仍然attach parent view的child item。 - 从ChildHelper的List
mHiddenViews中取那些被hidden但是没有被移除的view,然后通过view获取ViewHolder,顺带把ViewHolder添加到mAttachedScrap中。 - 从ArrayList
mCachedViews中取。
以上三个缓存中都取不到的话,直接从RecycledViewPool里面取,如果不是第一次创建的话,这里基本上都可以获取到缓存的ViewHolder。
后边通过bindViewHolder通知我们自己Adapter里面的bindViewHolder做数据和view的绑定。
4、总结
4.1 RecyclerView
RecyclerView直接控制View的add和remove。
RecyclerView中创建了Recycler,负责回收,在后续获取ViewHolder的时候,返回缓存的数据。
RecyclerView中创建了RecyclerViewDataObserver(AdapterDataObserver),用于接收数据变动的通知并处理,处理方式是请求更新(requestLayout)。
RecyclerView中创建了RecycledViewPool,用于缓存兜底。
RecyclerView中定义了静态虚类Adapter,用于客户端编写代码继承该类并覆写一些函数,获取数据集大小,创建ViewHolder,将ViewHolder与View数据绑定,回收回调等。
RecyclerView中定义了静态虚类ViewHolder,它持有一个View对象,并且可以通过这个View反向获取ViewHolder(getChildViewHolderInt),中间做过渡的是类是LayoutParams,也定义在RecyclerView中,并继承自ViewGroup.MarginLayoutParams。在View创建初期,会将这个LayoutParams设置到View上。ViewHolder还包含了View的位置和ViewType参数。
RecyclerView中定义了静态虚类LayoutManager,LinearLayoutManager,StaggeredGridLayoutManager都是继承自LayoutManager,它管理着View的状态,中间人是ChildHelper。