Home > Back-end >  What is the behavior of Jetpack Compose animations?
What is the behavior of Jetpack Compose animations?

Time:12-11

In my android project, I'm doing a simple Floating Action Button that can expand and show a list of buttons to perform different actions. To track the current state of the FAB, I have the next enum class

enum class FabState {
    Expanded,
    Collapsed
}

For displaying the Floating Action Button, I have the following Composable function:

@Composable
fun MultiFloatingActionButton(
    icon: ImageVector,
    iconTint: Color = Color.White,
    miniFabItems: List<MinFabItem>,
    fabState: FabState, //The initial state of the FAB
    onFabStateChanged: (FabState) -> Unit,
    onItemClick: (MinFabItem) -> Unit
) {
    val transition = updateTransition(targetState = fabState, label = "transition")

    val rotate by transition.animateFloat(label = "rotate") {
        when (it) {
            FabState.Collapsed -> 0f
            FabState.Expanded -> 315f
        }
    }

    val fabScale by transition.animateFloat(label = "fabScale") {
        when (it) {
            FabState.Collapsed -> 0f
            FabState.Expanded -> 1f
        }
    }

    val alpha by transition.animateFloat(label = "alpha") {
        when (it) {
            FabState.Collapsed -> 0f
            FabState.Expanded -> 1f
        }
    }

    val shadow by transition.animateDp(label = "shadow", transitionSpec = { tween(50) }) { state ->
        when (state) {
            FabState.Expanded -> 2.dp
            FabState.Collapsed -> 0.dp
        }
    }

    Column(
        horizontalAlignment = Alignment.End
    ) { // This is where I have my question, in the if condition
        if (fabState == FabState.Expanded || transition.currentState == FabState.Expanded) {
            miniFabItems.forEach { minFabItem ->
                MinFab( //Composable for creating sub action buttons
                    fabItem = minFabItem,
                    alpha = alpha,
                    textShadow = shadow,
                    fabScale = fabScale,
                    onMinFabItemClick = {
                        onItemClick(minFabItem)
                    }
                )
                Spacer(modifier = Modifier.size(16.dp))
            }
        }

        FloatingActionButton(
            onClick = {
                onFabStateChanged(
                    when (fabState) {
                        FabState.Expanded -> {
                            FabState.Collapsed
                        }
                        FabState.Collapsed -> {
                            FabState.Expanded
                        }
                    }
                )
            }
        ) {
            Icon(
                imageVector = icon,
                tint = iconTint,
                contentDescription = null,
                modifier = Modifier.rotate(rotate)
            )
        }
    }
}

The constants I defined are for animating the buttons that will show/hide depending on the FAB state.

When I first made the function, the original condition was giving me a different behavior, and playing around with all the posible conditions, I got 3 different results:

  • 1st condition: if (transition.currentState == FabState.Expanded) {...}

Result: animation not loading from collapsed to expanded, but it does from expanded to collapsed

  • 2nd condition: if (fabState == FabState.Expanded) {...}

Result: animation loading from collapsed to expanded, but not from expanded to collapsed

3rd condition (the one I'm using right now): if (fabState == FabState.Expanded || transition.currentState == FabState.Expanded) {...}

Result: animation loading in both ways

So my question is: how does every condition change the behavior of the animations?

Any help would be appreciated. Thanks in advance

CodePudding user response:

fabState is updated as soon as onFabStateChanged is called and transition.currentState is updated when it ends the transition and transition.isRunning returns false

Animation only happens if the composable is present in the tree. When the condition is false in the if block, the elements are not available for animation.

condition 1 false during the enter perion which breaks the enter animation and condition 2 is false during the exit period which breaks the exit animation and both are false after exit. Therefore merging them solved your issue and also removes the composables from the tree when not wanted.

Better approach

AnimatedVisibility(
    visible = fabState == FabState.Expanded,
    enter = fadeIn()  scaleIn(),
    exit = fadeOut()   scaleOut(),
) {
    miniFabItems.forEach { minFabItem ->
        MinFab(
            fabItem = minFabItem,
            textShadow = 0.dp,
            onMinFabItemClick = {
                onItemClick(minFabItem)
            }
        )
        Spacer(modifier = Modifier.size(16.dp))
    }
}

And use graphicsLayer modifier to instead of rotate

Icon(
    imageVector = Icons.Default.Add,
    tint = Color.White,
    contentDescription = null,
    modifier = Modifier
        .graphicsLayer {
            this.rotationZ = rotate
        }
)
  • Related