前言
滚轮应该是我们很经常用到一个控件了,比如日期选择,时间选择,地区选择等都习惯用滚轮来展示。
滚轮控件的识点
上图是由三个滚轮控件组成的日期选择器,以此我们分析所需要的知识点:
- 手势(滑动,惯性滚动)
- 内容循环滚动
- 实现滚轮样式
手势(滑动,惯性滚动)
我首先想到的是 Compose 的 滚动修饰符 效果如下:
接下来就是解决惯性问题了,我很自然想到列表组件 LazyColumn
就准备去看它的源码是怎么实现时。
好吧,就决定是你了 LazyColumn
内容循环滚动
既然决定使用 LazyColumn
那内容循环也变的简单,这里直接贴代码:
val size = data.size
val count = Int.MAX_VALUE
val startIndex = count / 2
val listState = rememberPagerState(initialPage = startIndex - startIndex % size)
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
) {
items(count) { index ->
......
}
}
实现滚轮样式
如何通过调整 LazyColumn
的 Item 项样式实现滚轮效果呢,这里放一张我画的草图:
由上面的草图我们发现关键点在 Item 项的旋转角度和平移距离。
原理竟然比草图还简单。
既然如此我们先拿到 Item 项的滑动时的偏移距离,直接贴代码:
val listState = rememberPagerState(initialPage = startIndex - startIndex % size)
val layoutInfo by remember { derivedStateOf { listState.layoutInfo } }
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
) {
items(count) { index ->
val item = layoutInfo.visibleItemsInfo.find { it.index == index }
if (item != null) {
val itemCenterY = item.offset + item.size / 2 //获取Item项的偏移距离
}
}
}
通过偏离距离计算调整系数。
/**
* pickerCenterLinePx 滚轮控件中线
* itemCenterY < pickerCenterLinePx 说明Item项在上半部,逐渐缩小,反之则逐渐放大
**/
currentsAdjust = 0.75f + 0.25f * if (itemCenterY < pickerCenterLinePx) {
itemCenterY / pickerCenterLinePx
} else {
1 - (itemCenterY - pickerCenterLinePx) / pickerCenterLinePx
}
最后按照惯例贴上完整代码。
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun <T> WheelPicker(
data: List<T>,
selectIndex: Int,
visibleCount: Int,
modifier: Modifier = Modifier,
onSelect: (index: Int, item: T) -> Unit,
content: @Composable (item: T) -> Unit,
) {
BoxWithConstraints(modifier = modifier, propagateMinConstraints = true) {
val density = LocalDensity.current
val size = data.size
val count = size * 10000
val pickerHeight = maxHeight
val pickerHeightPx = density.run { pickerHeight.toPx() }
val pickerCenterLinePx = pickerHeightPx / 2
val itemHeight = pickerHeight / visibleCount
val itemHeightPx = pickerHeightPx / visibleCount
val startIndex = count / 2
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = startIndex - startIndex.floorMod(size) + selectIndex,
initialFirstVisibleItemScrollOffset = ((itemHeightPx - pickerHeightPx) / 2).roundToInt(),
)
val layoutInfo by remember { derivedStateOf { listState.layoutInfo } }
LazyColumn(
modifier = Modifier,
state = listState,
flingBehavior = rememberSnapFlingBehavior(listState),
) {
items(count) { index ->
val currIndex = (index - startIndex).floorMod(size)
val item = layoutInfo.visibleItemsInfo.find { it.index == index }
var currentsAdjust = 1f
if (item != null) {
val itemCenterY = item.offset + item.size / 2
currentsAdjust = 0.75f + 0.25f * if (itemCenterY < pickerCenterLinePx) {
itemCenterY / pickerCenterLinePx
} else {
1 - (itemCenterY - pickerCenterLinePx) / pickerCenterLinePx
}
if (!listState.isScrollInProgress
&& item.offset < pickerCenterLinePx
&& item.offset + item.size > pickerCenterLinePx
) {
onSelect(currIndex, data[currIndex])
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(itemHeight)
.graphicsLayer {
alpha = currentsAdjust
scaleX = currentsAdjust
scaleY = currentsAdjust
rotationX = (1 + currentsAdjust) * 180
},
contentAlignment = Alignment.Center,
) {
content(data[currIndex])
}
}
}
}
}
private fun Int.floorMod(other: Int): Int = when (other) {
0 -> this
else -> this - floorDiv(other) * other
}
Thanks
以上就是本篇文章的全部内容,如有问题欢迎指出,我们一起进步。
如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧,您的鼓励是我前进的动力。
谢谢~~