作者:丨小夕
前言
滚轮经常在选择中用到,主要包括类型选择、省市区联动选择、年月日联动选择等。
项目中的WheelView一般都是ScrollView+LinearLayout组合完成的。
但是自定义起来比较复杂,也有一些优秀的第三方库DateSelecter 通过Adapter的思想来灵活解决自定义的问题。
但是既然用到了Adapter的思想,那为啥不利用RecyclerView来实现呢?,毕竟我们比较熟悉RecyclerView.Adapter 也方便和项目中现有的Adapter复用。
于是我基于RecyclerView实现了一个滚轮模块,这些是他的基础功能:
- 对
RecyclerView,Adapter低侵入性,逻辑单独封装成RecyclerWheelViewModule - 支持通过
Adapter自定义WheelView样式 - 支持横向和竖向
- 支持自定义
WheelView边框
同时在滚轮模块的基础上,实现了联动滚轮View的封装。
效果



滚轮模块的用法也很简单,同时侵入性极低,使用拓展就能recyclerView.setupWheelModule()将RecyclerView改造成WheelView:
它会返回RecyclerWheelViewModule 里面包含了操作滚轮模块的各种API
class XxxActivity {
val recyclerView: RecyclerView
val wheelAdapter =
BindingAdapter<String, ItemWheelVerticalBinding>(ItemWheelVerticalBinding::inflate) { _, item ->
itemBinding.text.text = item
itemBinding.text.setTextColor(if (isWheelItemSelected) Color.BLACK else Color.GRAY)
}
fun onCreate() {
recyclerView.adapter = wheelAdapter
val wheelModule = recyclerView.setupWheelModule()
wheelModule.apply {
offset = 1
orientation = RecyclerWheelViewModule.VERTICAL
setWheelDecoration(DefaultWheelDecoration(10.dp, 10.dp, 2.dp, "#dddddd".toColorInt()))
onSelectChangeListener = {
}
}
}
}
原理
WheelView的功能本身并不复杂,布局和滚动都是RecyclerView已经处理好的。
因此我们只需要解决一些WheelView的特性即可。
本着代码越少,Bug越上的原则。绝大多数特性都尽量使用RecyclerView提供的API,或者官方已有的模块去实现。
实现一个WheelView的功能主要完成以下实现:
- 选中的
Item居中显示,在滚动后自动居中选中的位置 Item的最上和最下有一定滚动间距,来使得最边缘的Item可以居中,同时让WheelView的尺寸恰好显示3个或5个Item- 支持绘制上下边界线来标识给用户滚轮选中区域
- 用户滑动时,更新选中位置,并刷新
Item数据,如加粗或者设置字体颜色为黑色 - 支持代码设置和获取当前选中位置
Item居中
WheelView在滚动停止后,会自动使得当前最靠近中间的Item滚动到布局的中心位置。
官方提供了 SnapHelpe 来帮助我们处理Item的对齐。
而它的子类 LinearSnapHelper 可以监听RecyclerView的滚动,在滚动结束后,使得最接近RecyclerView视图中心的那个Item的中心对齐到视图中心,简单描述就是它能使Item居中。
同时它也提供了很多有用的API的可重写的方法,我们通过重写onFling可以控制一下滚动速度。
class WheelLinearSnapHelper : LinearSnapHelper() {
override fun onFling(velocityX: Int, velocityY: Int): Boolean =
super.onFling(
(velocityX * flingVelocityFactor).toInt(),
(velocityY * flingVelocityFactor).toInt()
)
}
上下留白
这里的留白是指,在WheelView的开始和结束位置有一定的空白,以便于最后一个Item能滚动到中心。
因为留白的存在,当RecyclerView滚动到最上面时,第一个Item 刚好处于中间位置


上下留白的数量我们定义为offset ,从另外一个角度来看可以将留白看成offset个Header 和offset个Footer
这里的每个留白的高度一般就是Item的高度。
一般情况WheelView的每个Item的高度是一致的,我们取第一个Item的高度作为留白的高度。
以下是Header/Footer 的Adapter实现:
class OffsetAdapter(private val adapter: RecyclerView.Adapter<RecyclerView.ViewHolder>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
adapter.createViewHolder(parent, viewType)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (adapter.itemCount > 0) {
adapter.onBindViewHolder(holder, 0)
}
holder.itemView.visibility = View.INVISIBLE
measureItemSize(holder.itemView) //测量和记录一下留白的高度
}
override fun getItemCount(): Int = offset
}
通过ConcatAdapter 依次连接HeaderAdapter+数据Adapter+FooterAdapter 就实现了留白功能。
在项目中,我们想给RecyclerView 的开始和结束加padding 也可以通过这种方式。
然后重新设置RecyclerView 的Adapter。
fun setAdapter(adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder>?) {
val startOffsetAdapter = OffsetAdapter(adapter)
val endOffsetAdapter = OffsetAdapter(adapter)
recyclerView.adapter = ConcatAdapter(
startOffsetAdapter,
adapter,
endOffsetAdapter
)
}
一般情况下我们的WheelView的高度都是中间选中Item的高度加上上下额外显示offset个Item的高度。
也就是一共显示offset+1+offset个Item。
所以一般WheelView高度为(offset+1+offset)*itemSize
这里我们在OffsetAdapter测量出Item尺寸后顺便设置一下WheelView的高度。
fun measureItemSize(itemView: View) {
//....
itemSize = itemView.measuredHeight + margin
recyclerView.layoutParams = recyclerView.layoutParams.apply {
width = (offset + offset + 1) * itemSize
}
}
绘制边界线
边框是一般指WheelView中间有2条边界线,用来标识WheelView 选中区域。用来告知用户,滚动到这2个边界线中间的是选中的Item。

在RecyclerView中绘制在Item之上的内容我们可以使用RecyclerView.ItemDecoration,所以几乎也不需要我们实现。
我们主要主要根据itemSize 去计算当前上下边框位置来绘制即可。
class DrawableWheelDecoration(
val drawable: Drawable,
@Px private val size: Int,
) : WheelDecoration() {
override fun onDrawOver(c: Canvas, paren

本文介绍了如何基于RecyclerView实现滚轮模块,包括对RecyclerView的低侵入改造、自定义样式、方向支持以及边框绘制。此外,还详细讲解了如何实现联动滚轮,包括数据加载、联动逻辑和位置设置,展示了如何应用于省市区选择和年月日选择的场景。
最低0.47元/天 解锁文章
659

被折叠的 条评论
为什么被折叠?



