Изменить текст кнопки без изменения ширины кнопки?

Рассмотрим следующий код:

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 сохранял исходный размер (без установки фиксированного размера). Как мне это сделать?

0
0
130
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы можете установить 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 другие ответы более прагматичны и могут быть также хороши :-)

stefan.at.kotlin 22.04.2023 00:02

Другие вопросы по теме