Hey there, I’m encountering an issue with `Animate...
# compose
s
Hey there, I’m encountering an issue with
AnimatedVisibility
when using it within a
Column
that has a
verticalArrangement
+
spacing
. The animation appears to “jump” when the visibility state changes. However, when there’s no spacing, the animation is smooth. (Not asking for help, rather if a bug report should be filed or not). Code and Video in 🧵
In this example, there are two columns. The left column has spacing and exhibits the jumping behavior, while the right column has no spacing and animates smoothly.
Code:
Copy code
fun main() = singleWindowApplication(state = WindowState(width = 400.dp, height = 800.dp)) {
    AnimatedVisibilityJumpingDemo()
}

@Composable
fun AnimatedVisibilityJumpingDemo() {
    var isVisible by remember { mutableStateOf(true) }
    Column {
        Button(onClick = { isVisible = !isVisible }) {
            Text("Toggle red's visibility")
        }

        Row {
            Column(
                modifier = Modifier.weight(1f).padding(8.dp),
                verticalArrangement = Arrangement.spacedBy(32.dp)
            ) {
                Text("With Spacing between Boxes")
                AnimatedVisibility(isVisible) {
                    Box(
                        modifier = Modifier
                            .background(Color.Red)
                            .fillMaxWidth()
                            .height(40.dp)
                    )
                }

                Box(
                    modifier = Modifier
                        .background(Color.Green)
                        .fillMaxWidth()
                        .height(40.dp)
                )
            }

            Column(
                modifier = Modifier.weight(1f).padding(8.dp),
            ) {
                Text("No Spacing between Boxes")
                AnimatedVisibility(isVisible) {
                    Box(
                        modifier = Modifier
                            .background(Color.Red)
                            .fillMaxWidth()
                            .height(40.dp)
                    )
                }

                Box(
                    modifier = Modifier
                        .background(Color.Green)
                        .fillMaxWidth()
                        .height(40.dp)
                )
            }
        }
    }
}
I can understand where it is coming from (the spacing does not seem to be not taken into account when calculating the animation), but I’m not sure if this is the intended behavior or a bug. Should I file a bug report? Thx.
f
Does not seem like a bug to me. I think from the Columns point of view it does exactly what is expected: keeps spacing of 32 dp between children. It does not know about the child being animated away, only that the height changed and than it's removed from composition.
s
Agreed, only from a developers POV it might be a bit awkward, since it is not uncommon to have columns with spacing, and wanting to animate the visibility of a child. In that case it would require workarounds (remove the
verticalArrangement
and add manual
Spacers
, or similar). I have the gut feeling that the framework somehow “magically” should handle it. But I might be wrong.
s
Had this exact case come up, and yes in those cases you can’t use spacedBy arrangement really, since the child either exists or does not, the existence isn’t animated (hence the extra padding being added instantly), only the child space itself is. I did this where the items are not spaced by a spacedBy arrangement on the container, but each child handles its own spacing (Bonus if the spacings aren’t standard between items, this makes it easier to work with, and I generally prefer it. Read this for more thoughts about why which I agree with). So tl;dr put the spacing on the content of the Column instead, and make sure you pick how you want this animation to play out by know what you put inside the
AnimatedVisibility
and what you don’t.
the framework somehow “magically” should handle it
And about this, I am so glad it doesn’t. A lot of these “magic” things are good until you don’t want them and then you’re ducked.
f
A lot of these “magic” things are good until you don’t want them and then you’re ducked.
I couldn't agree more. This is my exact problem with SwiftUI. The "modifier based API" of SwiftUI is so confusing and IMO really bad compared to Compose.
a
Something I really like about most Compose APIs is that even in cases where there is “magic” or default behavior, you can drill down one layer, see what’s going on, and make your own customized version that has adjustments for precisely what you want. If there’s a modifier or component that doesn’t quite do what you want, take a look at its source and learn what it’s doing. The layering means that you can often trade one step in complexity for one step in customizability.
s
Dang, I see what I did there, so please strike the word “magical” 🙈 However, in that particular case I brought up, I still think that, from a “API-Users” point of view, I should not need to worry about making sure “where to put my Spacers / Paddings, and what I have to wrap in which container to make it work, …“, when the API clearly states `verticalArrangement = spacedBy(32.dp)`“. I can do that, for sure, the options for workarounds are countless - but my feeling is I should not need to. The Animations should take this into account IMHO. Btw, I LOVE Compose and also appreciate the fact that Compose is open for us all to dig into and learn about the inner workings. So much different than closed source SwiftUI (in which the animation indeed does what one would expect, however 🙈). So I can totally live with using a workaround here and there in exchange. Thanks for the responses, appreciate the viewpoints.
s
LookaheadLayout (very experimental) primitive is going to solve this exact issue. Instead of animating bounds/parameters independently, it captures snapshot of target layout for animation and animates toward that state. It is still not ready for production, e.g. subcomposition doesn't work with it yet, but you can try it out if you are interested
s
@shikasd Awesome, that looks fantastic, can’t wait to play around with it! Thanks for the heads up! (https://effectiveandroid.substack.com/p/introducing-lookaheadlayout)
d
AnimatedVisibility
's API contract is that it animates its child, it can't influence the spacing that its parent put between it and its siblings. Here I would consider it as WAI. I also agree that there should be an easy way to animate that spacing as well. What could be helpful here is a position animation for the sibling (i.e. the green bar), rather than relying on the derived position animation from AnimatedVisibility of the red bar. The lookahead system we are building is designed to solve this type of use cases. From your use case, it seems like we could use a notion of
disappearing
in lookahead pass so that it's excluded from the parent's (i.e. Column in this case) measure and place logic. That way the parent can skip the disappearing layout as well as the spacing when calculating final position.