不知道你是否曾遇到需要选择年龄,城市,日期等列表文本的情况呢?类似下图的效果:
9ns6q-7yj7x.gif
滑动列表时中间位置突出选中,两侧保持一致,如果是你来实现这个功能,会怎么去做呢?
本文带你用最快的速度最小的成本实现一个简单粗暴且稳定的文本选择器,语言我们使用kotlin,当然java也是可以的,这里它们几乎没有什么差异。
首先需要有滚动的功能,RecyclerView和scrollView都能满足需求,我们先尝试使用ScrollView+LinearLayout实现,至于RecyclerView我们后面会提到。
实现大体逻辑是 判断item是否滑到了中间位置,如果滑到了中间位置,那么这个item的字体变大(或者其它效果),否则恢复正常。
所以最核心的问题就是怎么判断item是否滑到了中间,目前我们已知
- 父布局的宽度:parentWidth (也就是scrollView或者LinearLayout的宽度)
- 每个孩子的宽度:itemWidth (也就是每个TextView的宽度)
- 孩子的数量:childCount
- 总宽度:itemWidth x childCount
(这里的宽度我们也可以理解为横坐标)
所以我们需要在滑动的时候先找到位于中间的宽度(坐标),再根据这个宽度找到对应的item,从而实现选中效果。
设想一下,最简单的情况,有四个item,ABCD,D暂不可见
每个item宽度都为 100,parentWidth为 300,totalWidth = 400,childCount = 4
现在把C移动到B的位置, 需要移动100,那么 对于C来讲它的中间位置就等于
var currentMiddleWidth = parentSize / 2 + distance --> 150 + 100 = 250
再用这个长度除以总长度就是占总长的一个百分比,最后乘以childCount 就得出索引值了
var selectorItemIndex = currentMiddleWidth / totalWidth * childCount = 2(下标从0开始),
正好对应的item就是C ,也就是说当我滑动 100像素时,这时候位于中间位置的元素就是C。
滑动前:
before_scroll.png
滑动后:
滑动的距离 scrollX = 100
after_scroll.png那么我们就可以得出根据移动的距离 scrollX算出处于中间位置的 item index的方法:
/**
* 根据滚动距离获取被选中Item的下标
*/
fun getSelectedIndexByScrollX(scrollX: Int, parentWidth: Int): Int {
var currentMiddleWidth: Float = (parentSize / 2 + scrollX).toFloat()
var childCount: Int = mDataList?.size ?: 0
return (currentMiddleWidth / mTotalSize * childCount).toInt()
}
拿到item后,设置它的选中效果,再将其它的item设置非选中效果即可
这样,我们就用ScrollView实现了一个简单版的文本选择器。
当然,它还存在很多不足,比如
1.只支持单一布局(滑动)
HorizontalScrollView : 横向
ScrollView : 纵向
没法直接使用一个View同时支持横竖向(虽然也可以自定义ScrollView;或者同时使用这两个View,再根据方向去隐藏或者显示)
2.性能问题
滑动的时候是遍历所有的item进行判断,如果item数量过多,那么势必影响性能。而且在首次加载时,是一口气创建所有的子view,存在OOM的风险。
综上2个问题,使用RecyclerView可以避免,首先RecyclerView可以支持横竖布局,其次RecyclerView内置的view缓存机制(view的回收复用)可以避免性能及内存问题,但这里又引发了新的问题,就是当item的width不是固定值,而是wrap_content时,totalSize没法计算,导致我们这套逻辑不适用,这个暂时不在本文讨论(如果讨论的话,标题就不是5分钟了)。
具体实现可以看我的demo。两套实现逻辑均有(默认是ScrollView),各属性均支持代码和XML设置。
https://github.com/jieluote/ItemSelecterBar.git
展望:
“万物皆可选”是我的设计初衷。这也是为什么起名字叫做 ItemSelecterBar而不是 TextSelector或者 NumberSelector的原因。后期不再是只针对Textview设置,可支持任何view的任何效果,这需要用到动画,比如中间位置的view放大(添加动画),两边的缩小(去除动画)。
另外,如果大家有了解过不错的开源库可以推荐下。
网友评论