Jetpack Compose实现仅在需要时滚动文本布局问题
实现WearOS(Jetpack Compose)固定头脚的自适应滚动文本布局
需求:固定头部与底部,中间文本区域实现长文本可滚动,短文本居中且不可滚动,仅当文本高度超出视口剩余空间(总高度-头部高度-底部高度)时才启用滚动。
当前代码能实现长文本滚动,但短文本仍处于可滚动状态,不符合需求,以下是解决方案:
修改后的完整代码
@Composable fun ShowTextWithHeader( text: String, headerLine: String = "Header Line", footerLine: String = "Footer Line", ) { val scrollState = rememberScrollState(0) Scaffold( modifier = Modifier.fillMaxSize(), positionIndicator = { PositionIndicator(scrollState) }, ) { innerPadding -> // 适配Scaffold的内边距,避免内容被系统栏遮挡 Box(modifier = Modifier.fillMaxSize().padding(innerPadding)) { val headerHeight = 30.dp val footerHeight = 30.dp PossiblyScrollableText(text, scrollState, headerHeight, footerHeight) Header(headerLine, headerHeight) Footer(footerLine, footerHeight) } } } @Composable private fun PossiblyScrollableText( text: String, scrollState: ScrollState, headerHeight: Dp, footerHeight: Dp, ) { val density = LocalDensity.current // 转换dp为像素值,用于计算可用高度 val headerPx = with(density) { headerHeight.toPx() } val footerPx = with(density) { footerHeight.toPx() } SubcomposeLayout(Modifier.fillMaxSize()) { constraints -> // 第一步:预测量文本的实际高度(不限制高度) val textPlaceable = subcompose("RawText") { Text(text = text) }.first().measure(constraints.copy(maxHeight = Constraints.Infinity)) // 计算可用高度:总高度减去头脚的像素高度 val availableHeight = constraints.maxHeight - headerPx - footerPx // 判断文本是否超出可用空间 val needsScroll = textPlaceable.height > availableHeight // 第二步:根据是否需要滚动,布局最终的文本容器 layout(constraints.maxWidth, constraints.maxHeight) { val textY = if (needsScroll) { // 长文本:从头部下方开始布局 headerPx.toInt() } else { // 短文本:垂直居中(总高度-文本高度)/2 (constraints.maxHeight - textPlaceable.height) / 2 } val finalTextPlaceable = subcompose("FinalText") { Text( text = text, modifier = Modifier.run { if (needsScroll) { this.verticalScroll(scrollState) .heightIn(max = availableHeight.toDp()) } else { this } } ) }.first().measure( if (needsScroll) { // 长文本限制最大高度为可用空间 constraints.copy(maxHeight = availableHeight.toInt()) } else { constraints } ) finalTextPlaceable.place(0, textY) } } } @Composable private fun BoxScope.Header( line: String, height: Dp, ) { Row( modifier = Modifier .fillMaxWidth() .height(height) .background(Color.Gray) .align(Alignment.TopCenter), verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.Center, ) { Text(line) } } @Composable private fun BoxScope.Footer( line: String, height: Dp, ) { Row( modifier = Modifier .fillMaxWidth() .height(height) .background(Color.Gray) .align(Alignment.BottomCenter), verticalAlignment = Alignment.Top, horizontalArrangement = Arrangement.Center, ) { Text(line) } } @Preview(widthDp = 200, heightDp = 150, backgroundColor = 0xff000000, showBackground = true) @Composable fun PreviewLongText() { ShowTextWithHeader( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", ) } @Preview(widthDp = 200, heightDp = 150, backgroundColor = 0xff000000, showBackground = true) @Composable fun PreviewShortText() { ShowTextWithHeader(text = "这是一段短文本") }
关键实现说明
- 预测量文本高度:通过
SubcomposeLayout先在无高度限制的情况下测量文本的实际渲染高度,再与可用空间(总高度-头脚高度)对比,判断是否需要滚动。 - 动态控制滚动状态:仅当文本高度超出可用空间时,才给
Text添加verticalScroll修饰符,并限制容器最大高度为可用空间;短文本则不添加滚动修饰符。 - 短文本居中处理:当不需要滚动时,通过计算布局坐标实现文本垂直居中,同时保持不可滚动状态。
- 适配系统内边距:处理
Scaffold的innerPadding,避免内容被系统状态栏等遮挡。
内容的提问来源于stack exchange,提问作者rslemos
相关产品推荐
相关产品推荐

