Jetpack Compose:如何为BasicTextField添加滚动条?
Jetpack Compose BasicTextField 添加滚动条的实现方案
问题描述
我希望在Jetpack Compose的BasicTextField中,当文本超出屏幕边界时添加滚动条,请问是否有简单的实现方式?我尝试自行实现了一个解决方案,但运行效果不理想,代码如下:
@Composable fun Modifier.simpleVerticalScrollbar( state: ScrollState, width: Dp = 8.dp ): Modifier { val targetAlpha = if (state.isScrollInProgress) 1f else 0f val duration = if (state.isScrollInProgress) 150 else 500 val alpha by animateFloatAsState( targetValue = targetAlpha, animationSpec = tween(durationMillis = duration), label = "" ) return drawWithContent { drawContent() val firstVisibleElementIndex = state.viewportSize val needDrawScrollbar = state.isScrollInProgress || alpha > 0.0f // Draw scrollbar if scrolling or if the animation is still running and lazy column has content if (needDrawScrollbar) { val elementHeight = this.size.height / state.maxValue val scrollbarOffsetY = firstVisibleElementIndex * elementHeight val scrollbarHeight = state.viewportSize * elementHeight drawRect( color = Color.Red, topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY), size = Size(width.toPx(), scrollbarHeight), alpha = alpha ) } } }
另外,请问是否存在像XML中那样的内置实现方式?
解决方案
一、修复自定义滚动条的问题
你实现的滚动条计算逻辑存在错误,ScrollState的viewportSize并非可见元素索引,而是与可见区域滚动范围比例相关的数值,正确计算需基于state.value(当前滚动偏移)和state.maxValue(最大滚动偏移):
修改后的代码如下:
@Composable fun Modifier.simpleVerticalScrollbar( state: ScrollState, width: Dp = 8.dp, color: Color = Color.Gray.copy(alpha = 0.5f) ): Modifier { val targetAlpha = if (state.isScrollInProgress || state.maxValue > 0) 1f else 0f val duration = if (state.isScrollInProgress) 150 else 500 val alpha by animateFloatAsState( targetValue = targetAlpha, animationSpec = tween(durationMillis = duration), label = "" ) return drawWithContent { drawContent() val needDrawScrollbar = alpha > 0.0f && state.maxValue > 0 if (needDrawScrollbar) { // 计算滚动条的高度和偏移 val scrollableHeight = this.size.height val scrollRatio = state.value.toFloat() / state.maxValue.toFloat() val scrollbarHeight = (scrollableHeight * (scrollableHeight / (scrollableHeight + state.maxValue))).coerceAtLeast(20.dp.toPx()) val scrollbarOffsetY = scrollRatio * (scrollableHeight - scrollbarHeight) drawRect( color = color, topLeft = Offset(this.size.width - width.toPx(), scrollbarOffsetY), size = Size(width.toPx(), scrollbarHeight), alpha = alpha, cornerRadius = CornerRadius(width.toPx() / 2) ) } } }
使用方式:
val scrollState = rememberScrollState() var text by remember { mutableStateOf("") } BasicTextField( value = text, onValueChange = { text = it }, modifier = Modifier .fillMaxWidth() .height(200.dp) .verticalScroll(scrollState) .simpleVerticalScrollbar(scrollState) )
二、使用内置滚动条实现
从Compose 1.2.0开始,官方提供了内置的VerticalScrollbar组件,无需自行实现,用法和XML中的滚动条逻辑一致:
val scrollState = rememberScrollState() var text by remember { mutableStateOf("") } Box { BasicTextField( value = text, onValueChange = { text = it }, modifier = Modifier .fillMaxWidth() .height(200.dp) .verticalScroll(scrollState) ) VerticalScrollbar( modifier = Modifier.align(Alignment.CenterEnd), adapter = rememberScrollbarAdapter(scrollState) ) }
该内置组件自带滚动显示、静止隐藏的动画,还可通过style参数自定义样式,稳定性和易用性远高于自定义实现。
内容的提问来源于stack exchange,提问作者Martynas B
相关产品推荐
相关产品推荐

