Recycler View的出现,无疑是令android 开发工程师兴奋了许久。不久前看ListView 源码的时候,看得晕乎乎的,滑动功能以及复用机制堆积在了同一个类里面,RecyclerView 很好的解决这种尴尬的情况。
下面先从功能上看一下各个类的功能:
RecyclerView 这个类相当于中心的管理者
LayoutManager 主要负责item 添加 ,删除,以及循环利用机制等功能
Recycler.SmoothScroller 主要是负责跟踪目标view的index 和 平滑滑动
Recycler.UpdateOp 主要负责标志view的行为,增加 删除 更新
Recycler.ViewHolder 这个类大家都很熟悉,没错他就是相当于ListView 的adapter 中的ViewHolder
Recycler.State 主要是保留RecyclerView的一些信息。
Recycler.Adapter 主要使将dataset数据与itemView 绑定在一起
Recycler.Recycler 主要是保留正在显示的View和已经废弃或者是可回收利用的view
上面是主要的类,这篇文章中,我们不涉及RecyclerView 相关的动画
入口函数是setAdapter 或者是setLayoutManager ,我们 用其中一个即可,下面是
public void setAdapter(Adapter adapter) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
}
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
// Since animations are ended, mLayout.children should be equal to recyclerView.children.
// This may not be true if item animator's end does not work as expected. (e.g. not release
// children instantly). It is safer to use mLayout's child count.
if (mLayout != null) {
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler, true);
}
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter);
mState.mStructureChanged = true;
markKnownViewsInvalid();
requestLayout();
}
最后requestLayout 会调用RecyclerView的onMeasure 和 onLayout() 方法
@Override
protected void onMeasure(int widthSpec , int heightSpec) {
if (mAdapterUpdateDuringMeasure) {
eatRequestLayout();
updateChildViews();
mAdapterUpdateDuringMeasure = false;
resumeRequestLayout(false);
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
}
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
final int widthSize = getMeasuredWidth();
final int heightSize = getMeasuredHeight();
if (mLeftGlow != null) {
mLeftGlow.setSize(heightSize, widthSize);
}
if (mTopGlow != null) {
mTopGlow.setSize(widthSize, heightSize);
}
if (mRightGlow != null) {
mRightGlow.setSize(heightSize, widthSize);
}
if (mBottomGlow != null) {
mBottomGlow.setSize(widthSize, heightSize);
}
}
mAdapterUpdateDuringMeasure 这个变量在下一篇文章中会追踪一下,我们这里就看主要的函数,下面我们来看一下,LinearLayoutManager 的 onMeasure 方法
public void onMeasure(Recycler recycler , State state , int widthSpec , int heightSpec) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final int widthSize = MeasureSpec.getSize(widthSpec);
final int heightSize = MeasureSpec.getSize(heightSpec);
int width = 0;
int height = 0;
switch (widthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
width = widthSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
width = getMinimumWidth();
break;
}
switch (heightMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
height = heightSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
height = getMinimumHeight();
break;
}
setMeasuredDimension(width, height);
}
可以看出,主要就是来设置RecyclerView的Size,那么下面来看一下RecyclerView的 Onlayout方法
@Override
protected void onLayout(boolean changed , int l , int t , int r , int b) {
eatRequestLayout();
dispatchLayout();
resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
看一下dispatchLayout()方法
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
return;
}
eatRequestLayout();
// simple animations are a subset of advanced animations (which will cause a
// prelayout step)
boolean animateChangesSimple = mItemAnimator != null && mItemsAddedOrRemoved && !mItemsChanged;
final boolean animateChangesAdvanced = ENABLE_PREDICTIVE_ANIMATIONS && animateChangesSimple && predictiveItemAnimationsEnabled();
mItemsAddedOrRemoved = mItemsChanged = false;
ArrayMap<View, Rect> appearingViewInitialBounds = null;
mState.mInPreLayout = animateChangesAdvanced;
mState.mItemCount = mAdapter.getItemCount();
if (animateChangesSimple) {
// Step 0: Find out where all non-removed items are, pre-layout
mState.mPreLayoutHolderMap.clear();
mState.mPostLayoutHolderMap.clear();
final int count = getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
final View view = holder.itemView;
mState.mPreLayoutHolderMap.put(holder, new ItemHolderInfo(holder, view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), holder.mPosition));
}
}
if (animateChangesAdvanced) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
mInPreLayout = true;
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
mInPreLayout = false;
appearingViewInitialBounds = new ArrayMap<View, Rect>();
for (int i = 0; i < getChildCount(); ++i) {
boolean found = false;
final View child = getChildAt(i);
for (int j = 0; j < mState.mPreLayoutHolderMap.size(); ++j) {
final ViewHolder holder = mState.mPreLayoutHolderMap.keyAt(j);
if (holder.itemView == child) {
found = true;
continue;
}
}
if (!found) {
appearingViewInitialBounds.put(child, new Rect(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()));
}
}
}
clearOldPositions();
dispatchLayoutUpdates();
mState.mItemCount = mAdapter.getItemCount();
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
animateChangesSimple = animateChangesSimple && mItemAnimator != null;
if (animateChangesSimple) {
// Step 3: Find out where things are now, post-layout
int count = getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(getChildAt(i));
final View view = holder.itemView;
mState.mPostLayoutHolderMap.put(holder, new ItemHolderInfo(holder, view.getLeft(), view.getTop(), view.getRight(), view.getBottom(), holder.mPosition));
}
// Step 4: Animate DISAPPEARING and REMOVED items
final int preLayoutCount = mState.mPreLayoutHolderMap.size();
for (int i = preLayoutCount - 1; i >= 0; i--) {
final ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i);
if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) {
final ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i);
mState.mPreLayoutHolderMap.removeAt(i);
final View disappearingItemView = disappearingItem.holder.itemView;
removeDetachedView(disappearingItemView, false);
mRecycler.unscrapView(disappearingItem.holder);
animateDisappearance(disappearingItem);
}
}
// Step 5: Animate APPEARING and ADDED items
final int postLayoutCount = mState.mPostLayoutHolderMap.size();
if (postLayoutCount > 0) {
for (int i = postLayoutCount - 1; i >= 0; i--) {
final ViewHolder itemHolder = mState.mPostLayoutHolderMap.keyAt(i);
final ItemHolderInfo info = mState.mPostLayoutHolderMap.valueAt(i);
if ((mState.mPreLayoutHolderMap.isEmpty() || !mState.mPreLayoutHolderMap.containsKey(itemHolder))) {
mState.mPostLayoutHolderMap.removeAt(i);
final Rect initialBounds = (appearingViewInitialBounds != null) ? appearingViewInitialBounds.get(itemHolder.itemView) : null;
animateAppearance(itemHolder, initialBounds, info.left, info.top);
}
}
}
// Step 6: Animate PERSISTENT items
count = mState.mPostLayoutHolderMap.size();
for (int i = 0; i < count; ++i) {
final ViewHolder postHolder = mState.mPostLayoutHolderMap.keyAt(i);
final ItemHolderInfo postInfo = mState.mPostLayoutHolderMap.valueAt(i);
final ItemHolderInfo preInfo = mState.mPreLayoutHolderMap.get(postHolder);
if (preInfo != null && postInfo != null) {
if (preInfo.left != postInfo.left || preInfo.top != postInfo.top) {
postHolder.setIsRecyclable(false);
if (DEBUG) {
Log.d(TAG, "PERSISTENT: " + postHolder + " with view " + postHolder.itemView);
}
if (mItemAnimator.animateMove(postHolder, preInfo.left, preInfo.top, postInfo.left, postInfo.top)) {
postAnimationRunner();
}
}
}
}
}
resumeRequestLayout(false);
mLayout.removeAndRecycleScrapInt(mRecycler, !animateChangesAdvanced);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
}
这段代码看起来很多,很多都是有关动画,今天我们的重点不是动画,所以找出其中的关键代码,
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
主要的layout 部分,我们来看一下LayoutManager的onLayoutChildren 方法,
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 render state
if (DEBUG) {
Log.d(TAG, "is pre layout:" + state.isPreLayout());
}
if (mPendingSavedState != null) {
setOrientation(mPendingSavedState.mOrientation);
setReverseLayout(mPendingSavedState.mReverseLayout);
setStackFromEnd(mPendingSavedState.mStackFromEnd);
mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
}
ensureRenderState();
// resolve layout direction
resolveShouldLayoutReverse();
// validate scroll position if exists
if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
// validate it
if (mPendingScrollPosition < 0 || mPendingScrollPosition >= state.getItemCount()) {
mPendingScrollPosition = RecyclerView.NO_POSITION;
mPendingScrollPositionOffset = INVALID_OFFSET;
if (DEBUG) {
Log.e(TAG, "ignoring invalid scroll position " + mPendingScrollPosition);
}
}
}
// this value might be updated if there is a target scroll position without an offset
boolean layoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
final boolean stackFromEndChanged = mLastStackFromEnd != mStackFromEnd;
int anchorCoordinate, anchorItemPosition;
if (mPendingScrollPosition != RecyclerView.NO_POSITION) {
// if child is visible, try to make it a reference child and ensure it is fully visible.
// if child is not visible, align it depending on its virtual position.
anchorItemPosition = mPendingScrollPosition;
if (mPendingSavedState != null) {
// Anchor offset depends on how that child was laid out. Here, we update it
// according to our current view bounds
layoutFromEnd = mPendingSavedState.mAnchorLayoutFromEnd;
if (layoutFromEnd) {
anchorCoordinate = mOrientationHelper.getEndAfterPadding() - mPendingSavedState.mAnchorOffset;
}
else {
anchorCoordinate = mOrientationHelper.getStartAfterPadding() + mPendingSavedState.mAnchorOffset;
}
}
else if (mPendingScrollPositionOffset == INVALID_OFFSET) {
final View child = findViewByPosition(mPendingScrollPosition);
if (child != null) {
final int startGap = mOrientationHelper.getDecoratedStart(child) - mOrientationHelper.getStartAfterPadding();
final int endGap = mOrientationHelper.getEndAfterPadding() - mOrientationHelper.getDecoratedEnd(child);
final int childSize = mOrientationHelper.getDecoratedMeasurement(child);
if (childSize > mOrientationHelper.getTotalSpace()) {
// item does not fit. fix depending on layout direction
anchorCoordinate = layoutFromEnd ? mOrientationHelper.getEndAfterPadding() : mOrientationHelper.getStartAfterPadding();
}
else if (startGap < 0) {
anchorCoordinate = mOrientationHelper.getStartAfterPadding();
layoutFromEnd = false;
}
else if (endGap < 0) {
anchorCoordinate = mOrientationHelper.getEndAfterPadding();
layoutFromEnd = true;
}
else {
anchorCoordinate = layoutFromEnd ? mOrientationHelper.getDecoratedEnd(child) : mOrientationHelper.getDecoratedStart(child);
}
}
else { // item is not visible.
if (getChildCount() > 0) {
// get position of any child, does not matter
final int pos = getPosition(getChildAt(0));
if (mPendingScrollPosition < pos == mShouldReverseLayout) {
anchorCoordinate = mOrientationHelper.getEndAfterPadding();
layoutFromEnd = true;
}
else {
anchorCoordinate = mOrientationHelper.getStartAfterPadding();
layoutFromEnd = false;
}
}
else {
anchorCoordinate = layoutFromEnd ? mOrientationHelper.getEndAfterPadding() : mOrientationHelper.getStartAfterPadding();
}
}
}
else {
// override layout from end values for consistency
if (mShouldReverseLayout) {
anchorCoordinate = mOrientationHelper.getEndAfterPadding() - mPendingScrollPositionOffset;
layoutFromEnd = true;
}
else {
anchorCoordinate = mOrientationHelper.getStartAfterPadding() + mPendingScrollPositionOffset;
layoutFromEnd = false;
}
}
}
else if (getChildCount() > 0 && !stackFromEndChanged) {
if (layoutFromEnd) {
final View referenceChild = getChildClosestToEnd();
anchorCoordinate = mOrientationHelper.getDecoratedEnd(referenceChild);
anchorItemPosition = getPosition(referenceChild);
}
else {
final View referenceChild = getChildClosestToStart();
anchorCoordinate = mOrientationHelper.getDecoratedStart(referenceChild);
anchorItemPosition = getPosition(referenceChild);
}
}
else {
anchorCoordinate = layoutFromEnd ? mOrientationHelper.getEndAfterPadding() : mOrientationHelper.getStartAfterPadding();
anchorItemPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}
detachAndScrapAttachedViews(recycler);
final int extraForStart;
final int extraForEnd;
final int extra = getExtraLayoutSpace(state);
final boolean before = state.getTargetScrollPosition() < anchorItemPosition;
if (before == mShouldReverseLayout) {
extraForEnd = extra;
extraForStart = 0;
}
else {
extraForStart = extra;
extraForEnd = 0;
}
// first fill towards start
updateRenderStateToFillStart(anchorItemPosition, anchorCoordinate);
mRenderState.mExtra = extraForStart;
if (!layoutFromEnd) {
mRenderState.mCurrentPosition += mRenderState.mItemDirection;
}
fill(recycler, mRenderState, state, false);
int startOffset = mRenderState.mOffset;
// fill towards end
updateRenderStateToFillEnd(anchorItemPosition, anchorCoordinate);
mRenderState.mExtra = extraForEnd;
if (layoutFromEnd) {
mRenderState.mCurrentPosition += mRenderState.mItemDirection;
}
fill(recycler, mRenderState, state, false);
int endOffset = mRenderState.mOffset;
// changes may cause gaps on the UI, try to fix them.
if (getChildCount() > 0) {
// because layout from end may be changed by scroll to position
// we re-calculate it.
// find which side we should check for gaps.
if (mShouldReverseLayout ^ mStackFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
}
// If there are scrap children that we did not layout, we need to find where they did go
// and layout them accordingly so that animations can work as expected.
// This case may happen if new views are added or an existing view expands and pushes
// another view out of bounds.
if (getChildCount() > 0 && !state.isPreLayout() && supportsPredictiveItemAnimations()) {
// to make the logic simpler, we calculate the size of children and call fill.
int scrapExtraStart = 0, scrapExtraEnd = 0;
final List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
final int scrapSize = scrapList.size();
final int firstChildPos = getPosition(getChildAt(0));
for (int i = 0; i < scrapSize; i++) {
final RecyclerView.ViewHolder scrap = scrapList.get(i);
final int position = scrap.getPosition();
final int direction = position < firstChildPos != mShouldReverseLayout ? RenderState.LAYOUT_START : RenderState.LAYOUT_END;
if (direction == RenderState.LAYOUT_START) {
scrapExtraStart += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
}
else {
scrapExtraEnd += mOrientationHelper.getDecoratedMeasurement(scrap.itemView);
}
}
if (DEBUG) {
Log.d(TAG, "for unused scrap, decided to add " + scrapExtraStart + " towards start and " + scrapExtraEnd + " towards end");
}
mRenderState.mScrapList = scrapList;
if (scrapExtraStart > 0) {
final View anchor = getChildClosestToStart();
updateRenderStateToFillStart(getPosition(anchor), startOffset);
mRenderState.mExtra = scrapExtraStart;
mRenderState.mAvailable = 0;
mRenderState.mCurrentPosition += mShouldReverseLayout ? 1 : -1;
fill(recycler, mRenderState, state, false);
}
if (scrapExtraEnd > 0) {
final View anchor = getChildClosestToEnd();
updateRenderStateToFillEnd(getPosition(anchor), endOffset);
mRenderState.mExtra = scrapExtraEnd;
mRenderState.mAvailable = 0;
mRenderState.mCurrentPosition += mShouldReverseLayout ? -1 : 1;
fill(recycler, mRenderState, state, false);
}
mRenderState.mScrapList = null;
}
mPendingScrollPosition = RecyclerView.NO_POSITION;
mPendingScrollPositionOffset = INVALID_OFFSET;
mLastStackFromEnd = mStackFromEnd;
mPendingSavedState = null; // we don't need this anymore
if (DEBUG) {
validateChildOrder();
}
}
这段代码已经在开始的时候标注了逻辑
// 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.
而2 ,3 就是关键的地方 ,这里包含循环利用的逻辑,那我们接下来就看这段代码。
/**
* The magic functions :). Fills the given layout, defined by the renderState. This is fairly independent from the
* rest of the {@link android.support.v7.widget.LinearLayoutManager} and with little change, can be made publicly
* available as a helper class.
*
* @param recycler Current recycler that is attached to RecyclerView
* @param renderState Configuration on how we should fill out the available space.
* @param state Context passed by the RecyclerView to control scroll steps.
* @param stopOnFocusable If true, filling stops in the first focusable new child
* @return Number of pixels that it added. Useful for scoll functions.
*/
private int fill(RecyclerView.Recycler recycler , RenderState renderState , RecyclerView.State state , boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = renderState.mAvailable;
if (renderState.mScrollingOffset != RenderState.SCOLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (renderState.mAvailable < 0) {
renderState.mScrollingOffset += renderState.mAvailable;
}
recycleByRenderState(recycler, renderState);
}
int remainingSpace = renderState.mAvailable + renderState.mExtra;
while (remainingSpace > 0 && renderState.hasMore(state)) {
final View view = renderState.next(recycler);
if (view == null) {
if (DEBUG) {
if (renderState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
break;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (!params.isItemRemoved() && mRenderState.mScrapList == null) {
if (mShouldReverseLayout == (renderState.mLayoutDirection == RenderState.LAYOUT_START)) {
addView(view);
}
else {
addView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
final int consumed = 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 (renderState.mLayoutDirection == RenderState.LAYOUT_START) {
bottom = renderState.mOffset;
top = renderState.mOffset - consumed;
}
else {
top = renderState.mOffset;
bottom = renderState.mOffset + consumed;
}
}
else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (renderState.mLayoutDirection == RenderState.LAYOUT_START) {
right = renderState.mOffset;
left = renderState.mOffset - consumed;
}
else {
left = renderState.mOffset;
right = renderState.mOffset + consumed;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:"
+ (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
renderState.mOffset += consumed * renderState.mLayoutDirection;
if (!params.isItemRemoved()) {
renderState.mAvailable -= consumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= consumed;
}
if (renderState.mScrollingOffset != RenderState.SCOLLING_OFFSET_NaN) {
renderState.mScrollingOffset += consumed;
if (renderState.mAvailable < 0) {
renderState.mScrollingOffset += renderState.mAvailable;
}
recycleByRenderState(recycler, renderState);
}
if (stopOnFocusable && view.isFocusable()) {
break;
}
if (state != null && state.getTargetScrollPosition() == getPosition(view)) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - renderState.mAvailable;
}
在fill 之前,先回收一下无用的数据(超出屏幕的itemView),然后使用 while (remainingSpace > 0 && renderState.hasMore(state)) 循环,添加数据直至超出屏幕。
这里主要就是循环复用机制 ,假如之前阅读过listview 循环复用机制,这里和listview是类似的。
这就是RecyclerView 的基础绘画流程