小屏设备上如何让Compose NumberPicker文本保持单行显示?
解决Jetpack Compose DurationPicker小屏数字换行问题
针对三星Galaxy S20这类小屏设备上DurationPicker的NumberPicker两位数字换行问题,下面提供几种自适应调整方案,既能保证小屏上数字单行显示,又能让大屏保持合理的间距:
方案一:基于ConstraintLayout动态计算单位文本padding
通过监听父容器尺寸,计算NumberPicker所需的最小宽度,动态调整"h/m/s"文本的padding,避免挤压数字显示空间:
@Composable fun DurationPicker( label: String, onSelectTime: (FullHourWithSec) -> Unit, value: FullHourWithSec, ) { OutlinedBox(label = label) { val density = LocalDensity.current val constraints = remember { mutableStateOf(IntSize.Zero) } val unitPadding = remember { mutableStateOf(8.dp) } ConstraintLayout( modifier = Modifier.fillMaxWidth() .onSizeChanged { size -> constraints.value = size // 计算两位数字+内部边距的最小宽度 val minPickerWidth = with(density) { val textWidth = TextMeasurer.measure( text = "00", style = MaterialTheme.typography.bodyLarge ).size.width.toDp() textWidth + 16.dp } // 计算布局所需的最小总宽度 val totalMinWidth = 3 * minPickerWidth + 3 * 8.dp // 空间不足时按比例缩减单位padding if (size.width.toDp() < totalMinWidth) { val extraGap = totalMinWidth - size.width.toDp() unitPadding.value = max(0.dp, 8.dp - extraGap / 3) } else { unitPadding.value = 8.dp } } ) { val (hourPicker, hourText, minutePicker, minuteText, secondPicker, secondText) = createRefs() NumberPicker( modifier = Modifier.constrainAs(hourPicker) { start.linkTo(parent.start) top.linkTo(parent.top) bottom.linkTo(parent.bottom) end.linkTo(hourText.start) width = Dimension.fillToConstraints }, label = { "%02d".format(it) }, value = value.hours, onValueChange = { onSelectTime(value.copy(hours = it)) }, dividersColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), range = 0..1000 ) Text( text = "h", modifier = Modifier.constrainAs(hourText) { start.linkTo(hourPicker.end) top.linkTo(parent.top) bottom.linkTo(parent.bottom) }.padding(horizontal = unitPadding.value) ) NumberPicker( modifier = Modifier.constrainAs(minutePicker) { start.linkTo(hourText.end) top.linkTo(parent.top) bottom.linkTo(parent.bottom) end.linkTo(minuteText.start) width = Dimension.fillToConstraints }, label = { "%02d".format(it) }, value = value.minutes, onValueChange = { onSelectTime(value.copy(minutes = it)) }, dividersColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), range = 0..59 ) Text( text = "m", modifier = Modifier.constrainAs(minuteText) { start.linkTo(minutePicker.end) top.linkTo(parent.top) bottom.linkTo(parent.bottom) }.padding(horizontal = unitPadding.value) ) NumberPicker( modifier = Modifier.constrainAs(secondPicker) { start.linkTo(minuteText.end) top.linkTo(parent.top) bottom.linkTo(parent.bottom) end.linkTo(secondText.start) width = Dimension.fillToConstraints }, label = { "%02d".format(it) }, value = value.seconds, onValueChange = { onSelectTime(value.copy(seconds = it)) }, dividersColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), range = 0..59 ) Text( text = "s", modifier = Modifier.constrainAs(secondText) { start.linkTo(secondPicker.end) top.linkTo(parent.top) bottom.linkTo(parent.bottom) end.linkTo(parent.end) }.padding(start = unitPadding.value) ) } } }
方案二:给NumberPicker设置最小宽度
预先计算两位数字的显示宽度,给每个NumberPicker设置最小宽度,确保数字有足够空间,剩余空间分配给单位文本的padding:
@Composable fun DurationPicker( label: String, onSelectTime: (FullHourWithSec) -> Unit, value: FullHourWithSec, ) { OutlinedBox(label = label) { val density = LocalDensity.current val rowWidth = remember { mutableStateOf(0.dp) } // 计算NumberPicker的最小宽度 val minPickerWidth = remember { with(density) { TextMeasurer.measure( text = "00", style = MaterialTheme.typography.bodyLarge ).size.width.toDp() + 16.dp } } // 计算单位文本的自适应padding val adaptivePadding = remember(rowWidth.value) { val availableExtra = rowWidth.value - 3 * minPickerWidth max(0.dp, availableExtra / 6) // 6个padding方向(h左右,m左右,s左) } Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth() .onSizeChanged { rowWidth.value = it.width.toDp() } ) { Box(modifier = Modifier.weight(1f).widthIn(min = minPickerWidth)) { NumberPicker( modifier = Modifier.fillMaxWidth(), label = { "%02d".format(it) }, value = value.hours, onValueChange = { onSelectTime(value.copy(hours = it)) }, dividersColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), range = 0..1000 ) } Text(text = "h", modifier = Modifier.padding(horizontal = adaptivePadding)) Box(modifier = Modifier.weight(1f).widthIn(min = minPickerWidth)) { NumberPicker( modifier = Modifier.fillMaxWidth(), label = { "%02d".format(it) }, value = value.minutes, onValueChange = { onSelectTime(value.copy(minutes = it)) }, dividersColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), range = 0..59 ) } Text(text = "m", modifier = Modifier.padding(horizontal = adaptivePadding)) Box(modifier = Modifier.weight(1f).widthIn(min = minPickerWidth)) { NumberPicker( modifier = Modifier.fillMaxWidth(), label = { "%02d".format(it) }, value = value.seconds, onValueChange = { onSelectTime(value.copy(seconds = it)) }, dividersColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), range = 0..59 ) } Text(text = "s", modifier = Modifier.padding(start = adaptivePadding)) } } }
方案三:文本自适应缩放(备选)
如果前两种方案无法满足需求,可以让NumberPicker的文本自动缩小以适应空间,但会降低小屏可读性,仅作为兜底方案:
@Composable fun DurationPicker( label: String, onSelectTime: (FullHourWithSec) -> Unit, value: FullHourWithSec, ) { OutlinedBox(label = label) { Row( verticalAlignment = Alignment.CenterVertically, ) { NumberPicker( modifier = Modifier.weight(1f), label = { num -> val text = "%02d".format(num) val textStyle = MaterialTheme.typography.bodyLarge val textLayoutResult = remember(text, textStyle) { TextMeasurer.measure(text = text, style = textStyle) } val textWidth = textLayoutResult.size.width val availableWidth = with(LocalDensity.current) { (LocalConfiguration.current.screenWidthDp - 48.dp).toPx() / 3 // 减去边距后分3份 } val scaleFactor = min(1f, availableWidth / textWidth) Text( text = text, style = textStyle, maxLines = 1, softWrap = false, modifier = Modifier.scale(scale = scaleFactor) ) }, value = value.hours, onValueChange = { onSelectTime(value.copy(hours = it)) }, dividersColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), range = 0..1000 ) Text(text = "h", modifier = Modifier.padding(horizontal = 8.dp)) // 分钟、秒的NumberPicker和文本组件重复上述逻辑即可 // ... } } }
内容的提问来源于stack exchange,提问作者paul
相关产品推荐
相关产品推荐

