美文网首页
Jetpack Compose : 超简单实现滚轮控件(Whee

Jetpack Compose : 超简单实现滚轮控件(Whee

作者: miaowmiaow | 来源:发表于2023-10-11 15:41 被阅读0次

    前言

    滚轮应该是我们很经常用到一个控件了,比如日期选择,时间选择,地区选择等都习惯用滚轮来展示。

    滚轮控件的识点

    05581a530c5545f58b14b26e5b91c8e2~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.jpg

    上图是由三个滚轮控件组成的日期选择器,以此我们分析所需要的知识点:

    1. 手势(滑动,惯性滚动)
    2. 内容循环滚动
    3. 实现滚轮样式

    手势(滑动,惯性滚动)

    我首先想到的是 Compose滚动修饰符 效果如下:

    gestures-simplescroll.gif

    接下来就是解决惯性问题了,我很自然想到列表组件 LazyColumn 就准备去看它的源码是怎么实现时。

    aee36980ee524ef6b1781134786f0959~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.jpg

    好吧,就决定是你了 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 项样式实现滚轮效果呢,这里放一张我画的草图:

    0837484cadf34fdeaebda7b17dd82d3c~tplv-k3u1fbpfcp-zoom-in-crop-mark_1512_0_0_0.jpg

    由上面的草图我们发现关键点在 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

    以上就是本篇文章的全部内容,如有问题欢迎指出,我们一起进步。
    如果觉得本篇文章对您有帮助的话请点个赞让更多人看到吧,您的鼓励是我前进的动力。
    谢谢~~

    源代码地址

    相关文章

      网友评论

          本文标题:Jetpack Compose : 超简单实现滚轮控件(Whee

          本文链接:https://www.haomeiwen.com/subject/xxtnbdtx.html