欢迎光临散文网 会员登陆 & 注册

DEVLOG 9.145 手写一个RecyclerView总结

2021-09-14 20:18 作者:房顶上的铝皮水塔  | 我要投稿

手写RecyclerView的内容参考了 手写RecyclerView

归纳一下手写RecyclerView中的一些重点和可能会遇到的面试题,这个总结主要谈谈RecyclerView的设计、View的复用、View边界判断的思路。手写的过程中遇到的问题也总结在这里:手写RecyclerView问题总结

Github代码连接:https://github.com/kolibreath/Practices/tree/master/MyRecyclerView

  1. 手写RecyclerView设计思路

    1. MyRecyclerView继承自ViewGroup

      手写RecyclerView继承自ViewGroup,众所周知,ViewGroup是默认设置了CLICKABLE 和 LONG_CLICKABLE = false,这样一来onTouchEvent中的super就回返回false,所以最简单的解决思路是在xml文件中设置MyRecyclerView的clickable=true。

    2. ViewGroup绘制的三大流程: onMeasure

      通常而言,ViewGroup首先应该对子View进行测量:调用measureChildren之类的方法测量子View。但是我们简化的MyRecyclerView在初始化的时候并不知道子View的个数,这个内容是通过Adapter传入的。所以我们的onMeasure没有重写:

         测量工作可以在onLayout中执行。我们默认子View的高度为一个固定值,这样比较方便。因为是指定的固定值,所以在测量的过程中我们设置测量模式为EXACTLY:

    

  

      c. ViewGroup绘制的三大流程: onLayout

        在写代码之前我们应当思考一下onLayout需要执行的任务

  1. 当第一次加载Data的时候,ViewGroup中没有任何内容,我们应当使用LayoutInflater加载View(先不考虑复用)。

  2. 使用LayoutInflater加载的View应该被添加到ViewGroup中。(addView)但是LayoutInflater并不会产生宽高,因为宽高的产生需要结合LayoutParams+父容器的MeasureSpec(mode+size)。

  3. 第一次加载,没有出现任何滑动行为时,我们通过屏幕的高度+View的高度推算应该现实的View个数。

  4. 当后续加载的过程中,先前显示在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的两个小细节:

  1. setTag的第一个参数int 会加载资源文件,所以不能给一个Int定值

  2. setTag使用<key-value>的模式,因为View.setTag也是非常常见的操作。如果在其他地方对于View进行了setTag,在这里又进行setTag,会改变预设的行为。

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

View的另外的draw流程让View自行完成,这里就不说了。

onLayout只是一个静态的行为,滑动的时候可以理解为滑动一点,onLayout一点。我们需要重写消息处理机制,这里主要是重写onInterceptTouchEvent和onTouchEvent。

  1. onInterceptTouchEvent:

    这个方法需要和onTouchEvent进行配合。我们现在在一个ViewGroup中,要想onTouchEvent被执行,我们需要返回true,表示不将消息交给子控件消费。当滑动的动作超过touchSlop时返回true,这样就会调用我们重写的onTouchEvent:

   2. onTouchEvent: 

超过TouchSlop 我直接滑动:

3. 重写ScrollBy 

因为scrollBy本身是通过调整canvas的位置进行View的滑动,所以我们不能使用这个方法。我们必须要重写。

重写思路:

  1. 如果MyRecyclerView向上滑动,下面需要新的ItemView进来补充,同时上面的View需要出去;向下滑动同理

  2. 如果MyRecyclerView向上滑动,但是已经是第一个View了,控制不能上划。同理,如果是最后一个View,我们不能下滑,所以我们可以定义scrollBound做边界控制:

    3. 上划需要从下面补充新的View;下滑需要从上面补充新的View,这也需要在scrollBy中实现。具体的逻辑我注释在代码中了:

滑动完成之后需要重新布局:


DEVLOG 9.145 手写一个RecyclerView总结的评论 (共 条)

分享到微博请遵守国家法律