在做了这么长时间的Android开发,还没有遇到过这个需求,不过看了别人的很多效果,感觉很棒,所以找了时间就研究了一下,现在做一些记录,等以后有了相关需要可以快速回顾。
在我学习的过程中,不可避免的遇到了很多问题,有的已经解决,有的还未解决,所以这个 Demo 就是看个乐呵吧。
自定义一个 LayoutManager 整体给我的感觉是与实现自定义 ViewGroup 的 onLayout 比较像。其他的测量绘制方法都不需要我们实现,测量方法还有很多可以直接使用的:
androidx.recyclerview.widget.RecyclerView.LayoutManager#measureChild
androidx.recyclerview.widget.RecyclerView.LayoutManager#measureChildWithMargins
这两个方法可以测量 child 的大小,一个不计算 child.layoutParams,一个计算。
测量完成之后,我们就可以获取 child 的大小了:
androidx.recyclerview.widget.RecyclerView.LayoutManager#getDecoratedMeasuredWidth
androidx.recyclerview.widget.RecyclerView.LayoutManager#getDecoratedMeasuredHeight
这里不直接使用 child.getMeasureWidth 显然是因为 RecyclerView 是有一个 ItemDecorate 可以设置,不设置就是获取的 getMeasureWidth 的值。
更完美的是,layout child 的时候,也有相应的方法:
androidx.recyclerview.widget.RecyclerView.LayoutManager#layoutDecoratedWithMargins
使用这个方法,我们就可以将 child 摆到我们想要摆放的位置了,我们只需要传递四大金刚的位置:l,t,r,b。网上看到一个效果就是,它计算出某个 path 的所有点,然后存起来,根据滚动的距离,来取相应的点,然后根据这个点以及child的大小,就可以将这个 child 摆到 path 的路径上,实现一个 item 跟随 path 的效果。只要想清楚了还是不难的。
了解这些,我们还需要了解一下 RecyclerView 的回收机制,因为 RecyclerView 最吊的地方就是回收复用,如果你搞了一个 LayoutManager 但是却无法回收复用,那岂不是很沙雕,关于回收这里就不仔细讲了,看[我的另一篇文章](https://github.com/aprz512/blog4aprz512/blob/master/Blog/Android-View/RecyclerView 的缓存机制.md) 吧。
有了上面这些基础,我们就可以开始动手写了。
首先,我们需要确定我们想要的效果,我们先看一下这个效果图:
[外链图片转存失败(img-FzCl7VoA-1565004606632)(https://github.com/aprz512/pic4aprz512/blob/master/Blog/Android-View/layoutmanager/Gif_20190805_140935.gif?raw=true)]
可以看到:
- 最左边,是有几个 item 堆在一起了的,那么是怎么实现的呢,其实就是 layout 的时候将 item 之间摆进一点就好了。比如:item 的宽度是 100,高度是 150,第一个item的位置为 【(0,0),(100, 150)】,第二个 item 的位置(假设 item 的 divider 宽度为 0)为 【(100, 0),(200, 150)】。但是我在摆第二个 item 的时候,我偏不从 100 开始摆,我从 20 开始摆,那么第二个 item 就叠在第一个 item 上面了。
- 右边的就简单了,按照通常的摆法就好了,不搞啥幺蛾子。
对效果了然于胸,我们就可以开始敲代码了,首先自然是继承父类:
androidx.recyclerview.widget.RecyclerView.LayoutManager#LayoutManager
它只有一个抽象方法:
override fun generateDefaultLayoutParams(): LayoutParams {
return LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT
)
}
首先,网上一致都是这样实现的,我就很蛋疼了,都不说为什么。
然后,我就去查看了注释,它是这样说的:为 RecyclerView 的 child 生成一个默认的 LayoutParams。那么为何要生成一个默认的 LayoutParams 呢?比如,有的同学在 Adapter 里面加载布局的时候,parent 会传 null,这个时候 child 的就是没有 LayoutParams 的,所以需要生成一个。同样的,PopupWindow 也会遇到。这里我就清楚了,传递 WRAP_CONTENT 是一个保险行为,有最好,没有就使用这个。
注释还说了,这个返回的是 RecyclerView.LayoutParams,所以如果你还想带一些自带的信息在 LayoutParams 里面,你可以继承这个 LayoutParams,然后实现下面3个方法:
androidx.recyclerview.widget.RecyclerView.LayoutManager#checkLayoutParams
androidx.recyclerview.widget.RecyclerView.LayoutManager#generateLayoutParams(android.view.ViewGroup.LayoutParams)
androidx.recyclerview.widget.RecyclerView.LayoutManager#generateLayoutParams(android.content.Context, android.util.AttributeSet)
就 OK 了。
搞定了唯一的一个抽象方法,我们就可以正常运行了,但是没啥效果,我们还需要实