0

Consider the following code:

val saveInProgress = false // in reality might come from a ViewModel

Button(onClick = {}) {
    val text = if(saveInProgress) "Save" else "..."
    Text(text)
}

When saveInProgress is set to true, the Button text gets shorter and the Button resizes. I want the Button to keep it's original size though (without setting a fixed size). How can I do that?

stefan.at.kotlin
  • 15,347
  • 38
  • 147
  • 270

3 Answers3

2

In Jetpack Version 1.4.x rememberTextMeasurer is introduced to measure text. By measuring your text you can get width even before laying outt anything in a 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()
    }
}

Style of button is required to measure text correctly.

enter image description here

Full sample

@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")
        }
    }
}

If version is lower than 1.4.0 and don't want to use experimental api you can use Modifier.onSizeChanged with another recomposition or SubcomposeLayout without another recomposition. SubcomposeLayout can be used to get dimensions of Composable even before it's measured and laid out like adding progress button inside Button while loading.

How to adjust size of component to it's child and remain unchanged when it's child size will change? (Jetpack Compose)

Thracian
  • 43,021
  • 16
  • 133
  • 222
  • accepted as answer due to all these great details/information and because it's a generic solution :-) yet for Googlers, the other answers are more pragmatic and might be also fine :-) – stefan.at.kotlin Apr 21 '23 at 22:02
1

You can set Modifier.widthIn(), so that the button's minimum width is not less than the specified value.

For example:

val saveInProgress = false

Button(
    onClick = {},
    modifier = Modifier.widthIn(min = 80.dp)
) {
    val text = if (saveInProgress) "Save" else "..."
    Text(text)
}
Victor Sklyarov
  • 827
  • 2
  • 3
  • 24
1

There are multiple solutions to this, you can for example use Modifier.onSizeChanged on the Button to keep track of max width and then apply that width. This will make sure that the Button never becomes smaller. Something like this:

var maxWidth by remember { mutableStateOf(0) }
Button(
    modifier = Modifier
        .onSizeChanged { maxWidth = maxOf(maxWidth, it.width) }
        .then(if (maxWidth > 0) Modifier.width(maxWidth) else Modifier)
) {}

But this is not ideal if your second text is gonna be longer, so maybe better solution is to put both texts inside the button and make one of them invisible, like this:

fun Modifier.invisible(isInvisible: Boolean) = drawWithContent {
  if (!isInvisible) {
    drawContent()
  }
}

Button {
    Box {
        Text("Save", Modifier.invisible(saveInProgress))
        Text("...", Modifier.invisible(!saveInProgress))
    }
}
Jan Bína
  • 3,447
  • 14
  • 16