AnimatedVisibility is not working with weights

90 Views Asked by At

Here is a simple example to reproduce the issue I'm having:

@Composable
fun AnimatedVisibilityEx(modifier: Modifier = Modifier) {
    var visible by remember { mutableStateOf(true) }

    Box(modifier = modifier.fillMaxSize()) {
        Column(horizontalAlignment = Alignment.CenterHorizontally) {

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
                    .background(Color.LightGray)
                    .border(width = 2.dp, color = Color.Blue, shape = CircleShape)
            )

            AnimatedVisibility(
                modifier = Modifier.weight(1f),
                visible = visible
            ) {
                Box(
                    modifier = Modifier
                        .fillMaxWidth()
                        .background(MaterialTheme.colorScheme.primary)
                )
            }

            Box(
                modifier = Modifier
                    .fillMaxWidth()
                    .weight(1f)
                    .background(Color.LightGray)
                    .border(width = 2.dp, color = Color.Red, shape = CircleShape)
            )

            Button(
                onClick = { visible = !visible },
                modifier = Modifier.padding(top = 32.dp)
            ) {
                Text(text = "Toggle Visible")
            }
        }
    }
}

I have 3 boxes with equal weights. The middle one is going away on button click and I want a nice animation - the middle one shrinks and the other 2 expand to equally take up the space. However, there is no animation running. When I remove the weights and give them heights then everything works fine. What's the problem and how to fix it?

2

There are 2 best solutions below

0
On

An ugly workaround is to remove the AnimatedVisibility and animate the weight instead:

...

    val animatedWeight: Float by animateFloatAsState(if (visible) 1f else 0.000001f)

    Box(
        modifier = Modifier
            .fillMaxWidth()
            .weight(animatedWeight)
            .background(MaterialTheme.colorScheme.primary)
    )

...

This, of course, will not allow you to use the rich set of enter/exit animations that AnimatedVisibility provides.

0
On

How about using BoxWithConstraints?.

@Composable
fun BoxAnim() {
    var middleBoxVisible by remember { mutableStateOf(true) }

    Column {
        BoxWithConstraints {
            val middleBoxWidth = if (middleBoxVisible) maxWidth / 3 else 0.dp
            val sideBoxWidth = (maxWidth - middleBoxWidth) / 2

            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.Center
            ) {
                Box(
                    modifier = Modifier
                        .width(sideBoxWidth)
                        .height(100.dp)
                        .background(Color.Red)
                )

                AnimatedVisibility(
                    visible = middleBoxVisible,
                    enter = slideInHorizontally() + expandHorizontally(),
                    exit = shrinkHorizontally() + slideOutHorizontally()
                ) {
                    Box(
                        modifier = Modifier
                            .width(middleBoxWidth)
                            .height(100.dp)
                            .background(Color.Green)
                    )
                }

                Box(
                    modifier = Modifier
                        .width(sideBoxWidth)
                        .height(100.dp)
                        .background(Color.Blue)
                )
            }
        }

        Button(
            modifier = Modifier
                .width(200.dp)
                .height(80.dp),
            onClick = { middleBoxVisible = !middleBoxVisible }) {
            Text(text = "Toggle Middle Box")
        }
    }
    
}

The BoxWithConstraints composable is used to get the maximum width of the screen. We want to show 3 boxes equally on screen so maxWidth/3 for each of them. In case the middle is not visible we recalculate the width of the other two boxes maxWidth/2.

Update 2: Based on the comment we can measure text width and then decide the width of the middle box

@Composable
fun BoxAnim2() {
    var middleBoxVisible by remember { mutableStateOf(true) }

    var middleBoxText by remember { mutableStateOf("Hello, World! Raghunandan") } // Initial text

    val textStyle = LocalTextStyle.current

    val textMeasurer = rememberTextMeasurer()
    val textMeasured = remember(textStyle, textMeasurer) {
        textMeasurer.measure(
            text = middleBoxText,
            style = textStyle.copy(textAlign = TextAlign.Center)
        ).size.width
    }

    val newWidth = with(LocalDensity.current) { textMeasured.toDp() + 8.dp }

    Column {
        BoxWithConstraints {

            val middleBoxWidth = if (middleBoxVisible) newWidth else 0.dp
            val sideBoxWidth = ((maxWidth) - middleBoxWidth) / 2

            Row(
                modifier = Modifier.fillMaxWidth(),
                horizontalArrangement = Arrangement.Center
            ) {
                Box(
                    modifier = Modifier
                        .width(sideBoxWidth)
                        .height(100.dp)
                        .background(Color.Red)
                )

                AnimatedVisibility(
                    visible = middleBoxVisible,
                    enter =  expandHorizontally(),
                    exit = shrinkHorizontally() 
                ) {
                    Box(
                        modifier = Modifier
                            .wrapContentWidth()
                            .height(100.dp)
                            .background(Color.Green)
                    ) {
                        Text(
                            maxLines =  1,
                            text = middleBoxText,
                            modifier = Modifier.padding(8.dp)
                        )
                    }
                }

                Box(
                    modifier = Modifier
                        .width(sideBoxWidth)
                        .height(100.dp)
                        .background(Color.Blue)
                )
            }
        }

        Button(
            modifier = Modifier
                .width(200.dp)
                .height(80.dp),
            onClick = { middleBoxVisible = !middleBoxVisible }) {
            Text(text = "Toggle Middle Box")
        }
    }

}