关于 Compose 中无限滚轮的简单实现方式

393 阅读1分钟

效果

picker.gif

实现

@Composable
fun WheelPicker(
    count: Int,
    onCheckedChange: (Int) -> Unit,
    modifier: Modifier = Modifier,
    showCount: Float = 5f,
    backgroundColor: Color = MaterialTheme.colorScheme.surface,
    contentColor: Color = contentColorFor(backgroundColor),
    child: @Composable BoxScope.(index: Int) -> Unit,
) {
    BoxWithConstraints(modifier = modifier.background(backgroundColor)) {
        val density = LocalDensity.current
        val height = density.run { maxHeight.toPx() }
        val itemHeight = height / showCount
        val state = rememberLazyListState(
            initialFirstVisibleItemIndex = Int.MAX_VALUE / 2 - Int.MAX_VALUE / 2 % count,
            initialFirstVisibleItemScrollOffset = ((itemHeight - height) / 2).roundToInt(),
        )

        LaunchedEffect(Unit) {
            snapshotFlow {
                state.isScrollInProgress
            }.filterNot { it }.onEach {
                (state.firstVisibleItemIndex + state.layoutInfo.visibleItemsInfo.size / 2).let {
                    onCheckedChange.invoke(it % count)
                }
            }.launchIn(this)
        }

        CompositionLocalProvider(LocalContentColor provides contentColor) {
            LazyColumn(
                modifier = modifier.fillMaxSize(),
                state = state,
                flingBehavior = rememberSnapFlingBehavior(state),
            ) {
                items(Int.MAX_VALUE) { index ->
                    /*var offset by remember { mutableStateOf(Rect.Zero) }
                    val fraction = 1 - (offset.center.y - height / 2).absoluteValue / height * 2*/
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                            .height(maxHeight / showCount),
                        /*.onGloballyPositioned { offset = it.boundsInParent() }
                        .alpha(0.2f + fraction * 0.8f)*/
                        contentAlignment = Alignment.Center,
                    ) {
                        child(index = index % count)
                    }
                }
            }
        }

        // 半透明遮罩
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(
                    brush = Brush.verticalGradient(
                        colors = listOf(backgroundColor, Color.Transparent, backgroundColor),
                    ),
                )
        )
    }
}

使用

WheelPicker(
    count = 47,
    onCheckedChange = {
        "onCheckedChange - $it".let(::println)
    },
    modifier = Modifier
        .fillMaxWidth()
        .height(300.dp),
) {
    Text(text = "ITEM_$it")
}