Hi guys, I am learning basic animation and tried s...
# compose
k
Hi guys, I am learning basic animation and tried some work. I see in the layout inspector it recompose a lot. So can you guys guide me on this how to minimise the recompose count in this code.
Copy code
@Preview(showBackground = true)
@Composable
fun MoveText() {
    val density = LocalDensity.current
    var columnHeightDp by remember {
        mutableStateOf(0.dp)
    }
    var visible by remember { mutableStateOf(true) }
    val iconOffsetAnimation: Dp by animateDpAsState(
        if (visible) 13.dp else 0.dp, tween(1000)
    )
    val textOffsetAnimation: Dp by animateDpAsState(
        if (visible) 6.dp else 0.dp, tween(1000)
    )
    val viewAlpha: Float by animateFloatAsState(
        targetValue = if (visible) 1f else 0f, animationSpec = tween(
            durationMillis = 1000,
        )
    )
    val heightInDp: Dp by animateDpAsState(
        targetValue = if (visible) {
            columnHeightDp
        } else {
            0.dp
        }, animationSpec = tween(
            durationMillis = 1000,
        )
    )
    ScrollComposeTheme {
        Column(
            modifier = Modifier
                .fillMaxWidth()
                .padding(start = 16.dp, top = 16.dp)
        ) {
            Column(
                modifier =
                if (columnHeightDp != 0.dp) {
                    Modifier
                        .height(heightInDp)
                } else {
                    Modifier
                        .onSizeChanged {
                            with(density) {
                                columnHeightDp = it.height.toDp()
                            }
                        }
                        .wrapContentHeight()
                }
                    .background(Color.LightGray)
            ) {
                Image(
                    modifier = Modifier.padding(top = iconOffsetAnimation),
                    alpha = viewAlpha,
                    imageVector = Icons.Default.ShoppingCart,
                    contentDescription = null,
                )
                Text(
                    modifier = Modifier.padding(top = textOffsetAnimation),
                    text = "Hello, Anna",
                    fontSize = 20.sp,
                    color = Color.Black.copy(alpha = viewAlpha),
                )
            }
            Button(
                modifier = Modifier.padding(top = 10.dp),
                onClick = {
                    visible = !visible
                },
            ) {
                Text(text = "Move Text")
            }
        }
    }
}
Screenshot 2023-03-07 at 14.28.02.png
z
You want to avoid reading the animated states from the composition directly. For padding, you can create a custom PaddingValues object that returns the animated values. The padding modifier will only read those values during layout, not composition. For the height, you probably need to use the layout modifier since there’s no lazy height. But then you can also merge your onSizeChanged logic into that layout modifier.
k
PaddingViewAnimationStateLess
Copy code
@Composable
fun PaddingViewAnimationStateLess(
    iconOffsetAnimation: () -> Dp,
    visible: Boolean,
    onVisibleChange: (Boolean) -> Unit,
) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(start = 16.dp, top = 16.dp)
    ) {
        Image(
            modifier = Modifier.padding(top = iconOffsetAnimation()),
            imageVector = Icons.Default.ShoppingCart,
            contentDescription = null,
        )
        Button(
            modifier = Modifier.padding(top = 10.dp),
            onClick = {
                onVisibleChange(!visible)
            },
        ) {
            Text(text = "Move Text")
        }
    }
}
Screenshot 2023-03-09 at 20.45.26.png
b
Modifier.padding()
runs in the composition phase so when you do
Modifier.padding(top = iconOffsetAnimation())
you are reading state in composition and when it changes, you will get a recomposition. That deferred read with the lambda isn't actually helping you because of that.
There isn't anything wrong with recomposition though, your original code is working as intended
To know what phase a modifier runs in, you basically can tell just from the fact that any modifier that has
()
is running in composition. Any modifier that uses
{}
is most likely running in one of the other phases
There is no
Modifier.padding {}
for you to be able to call your lambda in. Probably the easiest way to optimise this if you were doing it just for learning would be to just build a custom layout using
Modifier.layout {}
and make sure to read your animating state only in there
k
Thanks for great explanation about composition phase. Yes I am learning all these stuffs
Sure, I'll try to use
Modifer.layout
and try to learn.
Great help Ben ✌️
z
Great suggestion about just using
layout
, much better for learning. For the record though, there’s a
Modifier.padding()
overload that takes a
PaddingValues
object.
PaddingValues
is just an interface that has functions to calculate padding for left, top, right, and bottom. Those functions are read inside a
layout
modifier. So you can create a class that implements that interface, and reads your animation state in each of those functions, and those state reads will happen inside, and only invalidate, the layout pass. • padding modifierPaddingValues