DEVLOG 9.145 手写一个RecyclerView总结
手写RecyclerView的内容参考了 手写RecyclerView
归纳一下手写RecyclerView中的一些重点和可能会遇到的面试题,这个总结主要谈谈RecyclerView的设计、View的复用、View边界判断的思路。手写的过程中遇到的问题也总结在这里:手写RecyclerView问题总结
Github代码连接:https://github.com/kolibreath/Practices/tree/master/MyRecyclerView

手写RecyclerView设计思路
MyRecyclerView继承自ViewGroup
手写RecyclerView继承自ViewGroup,众所周知,ViewGroup是默认设置了CLICKABLE 和 LONG_CLICKABLE = false,这样一来onTouchEvent中的super就回返回false,所以最简单的解决思路是在xml文件中设置MyRecyclerView的clickable=true。
ViewGroup绘制的三大流程: onMeasure
通常而言,ViewGroup首先应该对子View进行测量:调用measureChildren之类的方法测量子View。但是我们简化的MyRecyclerView在初始化的时候并不知道子View的个数,这个内容是通过Adapter传入的。所以我们的onMeasure没有重写:
测量工作可以在onLayout中执行。我们默认子View的高度为一个固定值,这样比较方便。因为是指定的固定值,所以在测量的过程中我们设置测量模式为EXACTLY:
c. ViewGroup绘制的三大流程: onLayout
在写代码之前我们应当思考一下onLayout需要执行的任务
当第一次加载Data的时候,ViewGroup中没有任何内容,我们应当使用LayoutInflater加载View(先不考虑复用)。
使用LayoutInflater加载的View应该被添加到ViewGroup中。(addView)但是LayoutInflater并不会产生宽高,因为宽高的产生需要结合LayoutParams+父容器的MeasureSpec(mode+size)。
第一次加载,没有出现任何滑动行为时,我们通过屏幕的高度+View的高度推算应该现实的View个数。
当后续加载的过程中,先前显示在ViewGroup中的内容应当被清除
因此,我们可以得出以下的代码:

但是,当我们滑动的时候向上滑动或者向下滑动,View可能会被移除掉,被移除掉的View我们通过Recycler管理。考虑Recycler中保存View的数据结构,使用Stack是最为合理的。假设我们使用ArrayList,需要考虑ArrayList的扩容行为;我们使用LinkedList,滑动手指过快,导致View被移除和补充得很快的时候,删除和添加行为需要链接节点,也不是最优的解决方案,所以使用Stack对于栈顶的操作是最合适的。
如果我们支持多种不同的ViewType,我们就需要一个二维数组(栈)。
所以在上面的代码makeAndSetupView(i, l,itemViewTop,r,itemViewBottom) 中我们通过obtain来获取缓存的View。在得到这个View对象之后在进行layout。
这里我想说明一点,MyAdapter是我们实现的一个接口类,里面定义了很多回调函数。其中
onBindViewHolder还有onCreateViewHolder都应该返回ViewHolder对象,但是我们为了方便期间直接返回View。 并且onBindViewHolder应该是返回Unit(void),但是在这里如果返回Unit,还需要重新绑定一遍,我觉得也没有必要,于是采用了视频中的写法。 这里还需要注意setTag的两个小细节:
setTag的第一个参数int 会加载资源文件,所以不能给一个Int定值
setTag使用<key-value>的模式,因为View.setTag也是非常常见的操作。如果在其他地方对于View进行了setTag,在这里又进行setTag,会改变预设的行为。

obtain获取缓存的View。我们需要重写removeView,以便存放View到Recycler中。

View的另外的draw流程让View自行完成,这里就不说了。
onLayout只是一个静态的行为,滑动的时候可以理解为滑动一点,onLayout一点。我们需要重写消息处理机制,这里主要是重写onInterceptTouchEvent和onTouchEvent。
onInterceptTouchEvent:
这个方法需要和onTouchEvent进行配合。我们现在在一个ViewGroup中,要想onTouchEvent被执行,我们需要返回true,表示不将消息交给子控件消费。当滑动的动作超过touchSlop时返回true,这样就会调用我们重写的onTouchEvent:
2. onTouchEvent:
超过TouchSlop 我直接滑动:
3. 重写ScrollBy
因为scrollBy本身是通过调整canvas的位置进行View的滑动,所以我们不能使用这个方法。我们必须要重写。
重写思路:
如果MyRecyclerView向上滑动,下面需要新的ItemView进来补充,同时上面的View需要出去;向下滑动同理
如果MyRecyclerView向上滑动,但是已经是第一个View了,控制不能上划。同理,如果是最后一个View,我们不能下滑,所以我们可以定义scrollBound做边界控制:
3. 上划需要从下面补充新的View;下滑需要从上面补充新的View,这也需要在scrollBy中实现。具体的逻辑我注释在代码中了:
滑动完成之后需要重新布局: