Рассмотрим следующий код:
val saveInProgress = false // in reality might come from a ViewModel
Button(onClick = {}) {
val text = if (saveInProgress) "Save" else "..."
Text(text)
}
Когда для saveInProgress
установлено значение true, текст Button
становится короче, а размер Button
изменяется. Я хочу, чтобы Button
сохранял исходный размер (без установки фиксированного размера). Как мне это сделать?
Вы можете установить Modifier.widthIn(), чтобы минимальная ширина кнопки была не меньше указанного значения.
Например:
val saveInProgress = false
Button(
onClick = {},
modifier = Modifier.widthIn(min = 80.dp)
) {
val text = if (saveInProgress) "Save" else "..."
Text(text)
}
Для этого есть несколько решений, вы можете, например, использовать Modifier.onSizeChanged
на кнопке, чтобы отслеживать максимальную ширину, а затем применять эту ширину. Это гарантирует, что кнопка никогда не станет меньше. Что-то вроде этого:
var maxWidth by remember { mutableStateOf(0) }
Button(
modifier = Modifier
.onSizeChanged { maxWidth = maxOf(maxWidth, it.width) }
.then(if (maxWidth > 0) Modifier.width(maxWidth) else Modifier)
) {}
Но это не идеально, если ваш второй текст будет длиннее, поэтому, возможно, лучшим решением будет поместить оба текста внутри кнопки и сделать один из них невидимым, например:
fun Modifier.invisible(isInvisible: Boolean) = drawWithContent {
if (!isInvisible) {
drawContent()
}
}
Button {
Box {
Text("Save", Modifier.invisible(saveInProgress))
Text("...", Modifier.invisible(!saveInProgress))
}
}
В Jetpack версии 1.4.x для измерения текста введена функция RememberTextMeasurer. Измеряя свой текст, вы можете получить ширину еще до того, как что-либо выложите в Composable as
val text = "Initial Text Size"
val density = LocalDensity.current
val textMeasurer: TextMeasurer = rememberTextMeasurer()
val style: TextStyle = MaterialTheme.typography.button
val initialWidthDp: Dp = remember(text) {
with(density) {
textMeasurer.measure(
text = text,
style= style,
).size.width.toDp()
}
}
Стиль кнопки необходим для правильного измерения текста.
Полный образец
@Preview
@Composable
private fun TextWidthSample() {
val text = "Initial Text Size"
val density = LocalDensity.current
val textMeasurer: TextMeasurer = rememberTextMeasurer()
val style: TextStyle = MaterialTheme.typography.button
val initialWidthDp: Dp = remember(text) {
with(density) {
textMeasurer.measure(
text = text,
style = style,
).size.width.toDp()
}
}
var isLoading by remember {
mutableStateOf(false)
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(20.dp)
) {
Button(
onClick = { /*TODO*/ }
) {
Text(
modifier = Modifier
.width(initialWidthDp),
text = if (isLoading) "Loading" else text,
textAlign = TextAlign.Center
)
}
Spacer(modifier = Modifier.height(20.dp))
Button(
onClick = { isLoading = isLoading.not() }
) {
Text("Loading: $isLoading")
}
}
}
Если версия ниже 1.4.0 и вы не хотите использовать экспериментальный API, вы можете использовать Modifier.onSizeChanged с другой рекомпозицией или SubcomposeLayout без другой рекомпозиции. SubcomposeLayout можно использовать для получения размеров Composable еще до того, как он будет измерен и размещен, например, добавление кнопки прогресса внутри кнопки во время загрузки.
принят в качестве ответа из-за всех этих замечательных деталей / информации и потому, что это общее решение :-), но для сотрудников Google другие ответы более прагматичны и могут быть также хороши :-)