摘要:其实通过父类的这个方法之后会调用它的方法,这个名字熟悉自定义的童鞋都知道了。
我一直在说RecyclerView是一个值得深入学习,甚至可以说是一门具有艺术性的控件。那到底哪里值得我们花时间去深入学习呢。没错了,就是源码的设计。但是看源码其实是一件不简单的事情,就拿RecyclerView的源码来说,打开源码一看,往下拉啊拉啊,我擦,怎么还没到头,汗....居然有12k+行。看到这里恐怕会吓一跳,就这么一个看似简单的控件就这么多行源码,这让我从何看起,一股畏惧感油然而生。
其实不需要害怕,我们不需要一开始就想完全弄懂它每一步怎么实现的,这样反而会造成只见森林不见树木的感觉。我们就把源码就当成一片森林来说吧。首先我们只需要先抓住一条路径去看,也就是带着一个问题去看,这样就能够把这条路径上的树都看明白了。就不会有只见森林不见树,一脸茫然了。当然我们大多数情况肯定是不满足于此一条路径,想完全看明白它是怎么实现的,那就继续另开路径(再带着另外一个问题),继续看这条路上的树。当你把每条路都走差不多了,再回头来看,就会发现你既见到了森林又见到了一颗颗清晰树木,犹如醍醐灌顶、豁然开朗。
说着很简单,但是不得不说看源码的过程还是有点小痛苦的。不过,不用慌,看完之后你所获得那种充实感和满足感会远远大于过程中的痛苦感。毕竟这是一个充满艺术感的控件嘛,值得我们去欣赏和学习。
从使用RecyclerView的时候,它的一个功能就让我感觉很这个控件不简单,不知道你和我想的是不是一样。那是什么功能呢?我们只需改变一行代码就可以直接设置它的ItemView为水平布局、垂直布局、表格布局以及瀑布流布局。这是ListView所不能做到的。用起来简单,其背后肯定有故事啊。那我们就以这条路为核心来看这片森林了。
二、开始寻路从哪里开始看呢?
1.我们先从setAdapter()看起,这个方法我们比较熟悉,在Activity中这是我们直接接触的方法。
/** *Replaces the current adapter with the new one and triggers listeners. */ public void setAdapter(Adapter adapter){ ..... //用一个新的设配器和触发器来替代目前正在使的 setAdapterInternal(adapter,false,true); //请求布局,直接调用View类的请求布局方法 requestLayout(); }
setAdapter里面主要做了两件事:
首先调用setAdapterInternal方法,目的是用一个新的设配器和触发器来替代目前正在使用的。
我们深入进去看看它做了什么?
对于熟悉了观察者设计模式的,可以从下面的代码看出来,其实里面有个操作是:
注销观察者(之前的设配器)和注册观察者(新的设配器)操作。简单的理解一下就是设配器观察者会监测一些对象的状态,当这些对象状态改变,它可以通过这种设计模式低耦合的做出相应的改变。最后调用markKnownViewsInvalid方法刷新一下视图。
如果你想深入了解观察者设计模式的可以看一下这篇文章
传送门:观察者设计模式
{ Adapter mAdapter; ...... private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious, boolean removeAndRecycleViews) { if (mAdapter != null) { mAdapter.unregisterAdapterDataObserver(mObserver); //注销观察者 mAdapter.onDetachedFromRecyclerView(this); //Called by RecyclerView when it stops observing this Adapter. } ...... mAdapterHelper.reset(); final Adapter oldAdapter = mAdapter; mAdapter = adapter; if (adapter != null) { adapter.registerAdapterDataObserver(mObserver); //注册观察者 adapter.onAttachedToRecyclerView(this); } ...... //刷新视图 markKnownViewsInvalid(); }
之后调用了 requestLayout方法请求重新布局。这个方法很关键,和我们的这次选的路是相通的。
@Override public void requestLayout() { if (mEatRequestLayout == 0 && !mLayoutFrozen) { super.requestLayout(); } else { mLayoutRequestEaten = true; } }
这么关键的方法代码却这么少?而且好像只做了一个操作?没错,表面上只调用了父类View的requestLayout方法。其实通过父类的这个方法之后会调用它的onLayout方法,这个名字熟悉自定义View的童鞋都知道了。但我们看父类View的onLayout方法其实是个空方法。也就是说最终需要由它的子类来重写,也即RecyclerVie调用自身的onLayout方法。
@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; }
onLayout又调用了dispatchLayout方法,来分发layout
void dispatchLayout() { ...... if (mState.mLayoutStep == State.STEP_START) { //分发第一步 dispatchLayoutStep1(); mLayout.setExactMeasureSpecsFrom(this); //分发第二步 dispatchLayoutStep2(); } ...... //分发第三步 dispatchLayoutStep3(); ...... }
它把这个分发的过程分为了三步走
step1:做一下准备工作:决定哪一个动画被执行,保存一些目前view的相关信息
private void dispatchLayoutStep1() { ...... if (mState.mRunSimpleAnimations) { // Step 0: Find out where all non-removed items are, pre-layout int count = mChildHelper.getChildCount(); for (int i = 0; i < count; ++i) { final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); final ItemHolderInfo animationInfo = mItemAnimator .recordPreLayoutInformation(mState, holder, ItemAnimator.buildAdapterChangeFlagsForAnimations(holder), holder.getUnmodifiedPayloads()); mViewInfoStore.addToPreLayout(holder, animationInfo); } ...... }
step2:找到实际的view和最终的状态后运行layout。
private void dispatchLayoutStep2() { eatRequestLayout(); onEnterLayoutOrScroll(); ...... mState.mInPreLayout = false; // Step 2: 运行layout mLayout.onLayoutChildren(mRecycler, mState); mState.mStructureChanged = false; .... resumeRequestLayout(false); }
这里面有个方法很关键了,就是下面这个onLayoutChildren,这个为什么关键呢,先提一下这个,待会要详细说的。
mLayout.onLayoutChildren(mRecycler, mState);
step3:做一些分发的收尾工作了,保存动画和一些其他的信息。和我们不同路,就不看它了。
看了这么多先喝一杯92年的肥宅快乐水压压惊吧~~,顺便看张图小结一下上面的过程
三、寻得果树之前说过RecyclerView和ListView最大的不同就是在它们的布局实现上。在ListView中布局是通过自身的layoutChildren方法实现的,但对于RecyclerView来说就不是了,那是谁来实现了呢?
这就要从刚才结束的onLayoutChildren方法说起了,它不是RecyclerView的类直接方法,它是RecyclerView的内部类LayoutManager的方法,顾名思义,就是布局管理者了。我们的RecyclerView布局就通过这个布局管理者来做了,把这样一个很重要的职责就交给它了。从而实现某种程度上的低耦合。
那我们继续走,它是怎么执行这一职责的。
但是点进去看onLayoutChildren方法,发现只有一行代码,而且还是打印的日志:必须重写这个方法。
public void onLayoutChildren(Recycler recycler, State state) { Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) "); }
那么既然要重写必须要寻找一个子类,所以这里我就找了一个子类LinearLayoutManager类,也是我们最常用的一种线性布局来看。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { ...... int startOffset; int endOffset; final int firstLayoutDirection; onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); ...... if (mAnchorInfo.mLayoutFromEnd) { // 底部向顶部的填充 updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; //填充 fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; final int firstElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForEnd += mLayoutState.mAvailable; } // 顶部向底部的填充 updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; //填充 fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; ...... } } else { ...... } ...... }
这个方法主要就是通过一个布局算法,实现itemView从顶部到底部或者底部到顶部的填充,并创建一个布局的状态。接下来看一下fill方法是怎么进行填充的。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { ...... //1.计算RecyclerView可用的布局宽或高 int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; //2.迭代布局item View while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); //3.布局item view layoutChunk(recycler, state, layoutState, layoutChunkResult); //4.计算布局偏移量 layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection; if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // we keep a separate remaining space because mAvailable is important for recycling //5.计算剩余的可用空间 remainingSpace -= layoutChunkResult.mConsumed; } ...... } return start - layoutState.mAvailable; }
fill方法总的来说用了5步实现了itemVIew的填充:
(1)计算RecyclerView可用的布局宽或高
(2)迭代布局item View
(3)布局itemview
(4)计算布局偏移量
(5)计算剩余的可用空间
fill方法又会循环的调用layoutChunk来进行itemView的布局,下面先看看layoutChunk的实现
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { //1.获取itemview View view = layoutState.next(recycler); ...... //2.获取itemview的布局参数 LayoutParams params = (LayoutParams) view.getLayoutParams(); //3.测量Item View measureChildWithMargins(view, 0, 0); //4.计算该itemview消耗的宽和高 result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view); int left, top, right, bottom; //5.按照水平或竖直方向布局来计算itemview的上下左右坐标 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 { ...... } 6.计算itemview的边界比如下划线和margin,从而确定itemview准确的位实现最终的布局 layoutDecoratedWithMargins(view, left, top, right, bottom); } result.mFocusable = view.hasFocusable(); }
在layoutChunk中首先从layoutState获取此时的itemview,然后根据获得的这个itemview获取它的布局参数和尺寸信息,并且判断布局方式(横向或者纵向),以此计算出itemview的上下左右坐标。最后调用layoutDecoratedWithMargins方法完成布局。
这样一看就对整个过程有了个清晰的认识了吧,有没有感觉设计的很优雅。
四、贯穿布局的一条线到这里已经算走完我们之前准备走的一条路了。但从开始到这里始终忽略了一个东西没有说,那就在布局过程的大多方法中的参数都有一个Recycler对象。这个Recycler是什么呢?
在使用RecyclerView的过程中,我们都知道Adapter被缓存的单位不再是普通的itemview了,而是一个ViewHolder。这是和listview的一个很大的不同。
public final class Recycler { final ArrayListmAttachedScrap = new ArrayList<>(); ArrayList mChangedScrap = null; final ArrayList mCachedViews = new ArrayList (); private final List mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); public View getViewForPosition(int position) { return getViewForPosition(position, false); } View getViewForPosition(int position, boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; } ViewHolder tryGetViewHolderForPositionByDeadline(int position, boolean dryRun, long deadlineNs) { ... } ......
在Recycler类的开始就看到mAttachedScrap、mChangedScrap、mCachedViews、 mUnmodifiableAttachedScrap这几个ViewHolder的列表对象,它们就是用来缓存ViewHolder的。
具体是怎么实现的这里就不做详细的解释了。因为这里一说又会牵涉到其他的点,子子孙孙无穷尽也,毕竟这是一个有艺术感的控件,不能指望一篇文章把它说透哈。
到这里我们就结束了我们对RecyclerView的的源码分析了。相信你看完会有所收获。
作者:錦小白
https://www.jianshu.com/p/102...
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/76915.html
阅读 3819·2021-09-06 15:00
阅读 2157·2019-08-30 15:53
阅读 3262·2019-08-23 16:44
阅读 931·2019-08-23 15:19
阅读 1371·2019-08-23 12:27
阅读 4174·2019-08-23 11:30
阅读 569·2019-08-23 10:33
阅读 356·2019-08-22 16:05