Search code examples
androidandroid-jetpack-composematerial-designandroid-buttonandroid-jetpack-compose-material3

Change button text without changing the button width?


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?


Solution

  • 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)