androidanimationandroid-jetpack-composeandroid-jetpack-compose-material3jetpack-compose-animation

AnimatedContent size change animation not working smoothly in Jetpack Compose dialog


I'm trying to animate a size change inside a Dialog in Jetpack Compose using AnimatedContent. The goal is to smoothly transition between two different UI states that have different heights. However, the size animation doesn't work correctly as it can be seen in the gif below.

Here is a simplified version of my code. The Box elements are just placeholders to test different heights.

@Composable
fun DialogWithAnimation() {

    var state by remember { mutableIntStateOf(0) }

    Dialog(onDismissRequest = {}) {
        Surface(shape = AlertDialogDefaults.shape) {
            Column {

                // Header
                Row(
                    modifier = Modifier
                        .height(40.dp)
                        .fillMaxWidth(),
                    horizontalArrangement = Arrangement.spacedBy(16.dp)
                ) {
                    MyHeaderButton(
                        modifier = Modifier.weight(1f),
                        isActive = state == 0,
                        label = "A"
                    ) { state = 0 }
                    MyHeaderButton(
                        modifier = Modifier.weight(1f),
                        isActive = state == 1,
                        label = "B"
                    ) { state = 1 }
                }

                AnimatedContent(
                    targetState = state,
                    label = "A-B-content",
                    transitionSpec = {
                        (fadeIn(animationSpec = tween(220)) togetherWith
                         fadeOut(animationSpec = tween(90)))
                    }
                ) { target ->

                    if (target == 0) {
                        Box(
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(100.dp)
                                .background(Color.Blue)
                        )
                    } else {
                        Box(
                            modifier = Modifier
                                .fillMaxWidth()
                                .height(150.dp)
                                .background(Color.Yellow)
                        )
                    }
                }
            }
        }
    }
}

I expected the content inside the dialog to animate smoothly when changing its height.
This is the current behavior:

enter image description here


Solution

  • With dynamic content
    output: https://imgur.com/a/qB7akQ6

    @Composable
    fun DialogWithAnimation(onDismissRequest: () -> Unit) {
        var tab by remember { mutableIntStateOf(0) }
    
        Dialog(onDismissRequest = onDismissRequest) {
            Surface(
                modifier = Modifier.padding(16.dp),
                shape = RoundedCornerShape(16.dp),
                tonalElevation = 4.dp
            ) {
                Column(
                    modifier = Modifier
                        .padding(16.dp)
                        .width(IntrinsicSize.Min)
                ) {
                    // Tab Buttons
                    Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
                        MyHeaderButton(
                            modifier = Modifier.weight(1f),
                            isActive = tab == 0,
                            label = "Short"
                        ) { tab = 0 }
    
                        MyHeaderButton(
                            modifier = Modifier.weight(1f),
                            isActive = tab == 1,
                            label = "Long"
                        ) { tab = 1 }
                    }
    
                    Spacer(Modifier.height(12.dp))
    
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                            .animateContentSize(
                                animationSpec = tween(
                                    durationMillis = 50,
                                    easing = LinearEasing
                                )
                            )
                            .clip(RoundedCornerShape(12.dp))
                            .background(Color.LightGray)
                    ) {
                         when (tab) {
                            0 -> {
                                ShortContent()
                            }
                            else ->{
                                LongContent()
                            }
                        }
    
    
                    }
                }
            }
        }
    }
    
    
    
    @Composable
    fun ShortContent() {
        Column(
            Modifier
                .fillMaxWidth()
                .background(Color.Blue)
                .padding(8.dp)
        ) {
            Text("Short content", color = Color.White)
        }
    }
    
    @Composable
    fun LongContent() {
        Column(
            Modifier
                .fillMaxWidth()
                .background(Color.Red)
                .padding(8.dp)
        ) {
            Text("This is longer content that will grow", color = Color.White)
            Spacer(modifier = Modifier.height(16.dp))
            Text("It should animate height linearly.", color = Color.White)
            Spacer(modifier = Modifier.height(16.dp))
            Text("No sudden push, just smooth expansion.", color = Color.White)
        }
    }
    
    
    @Composable
    fun MyHeaderButton(
        modifier: Modifier = Modifier,
        isActive: Boolean,
        label: String,
        onClick: () -> Unit
    ) {
        val backgroundColor by animateColorAsState(
            targetValue = if (isActive) MaterialTheme.colorScheme.primary else Color.Transparent,
            animationSpec = tween(durationMillis = 300),
            label = "bg-color"
        )
    
        val contentColor by animateColorAsState(
            targetValue = if (isActive) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onSurface,
            animationSpec = tween(durationMillis = 300),
            label = "text-color"
        )
    
        Surface(
            modifier = modifier
                .clip(RoundedCornerShape(8.dp))
                .clickable(onClick = onClick),
            color = backgroundColor,
            border = if (isActive) null else BorderStroke(1.dp, MaterialTheme.colorScheme.outline),
            tonalElevation = if (isActive) 2.dp else 0.dp,
            shadowElevation = 0.dp,
        ) {
            Box(
                modifier = Modifier
                    .padding(vertical = 8.dp, horizontal = 12.dp),
                contentAlignment = Alignment.Center
            ) {
                Text(
                    text = label,
                    color = contentColor,
                    style = MaterialTheme.typography.labelLarge
                )
            }
        }
    }