androidkotlinandroid-jetpack-composeandroid-jetpackjetpack-compose-animation

How to reduce the number of recompositions for an animatable outlined icon color in Jetpack Compose?


To reduce color animation recompositions, I usually use drawRest in drawBehind, but what if I have an icon? https://developer.android.com/jetpack/compose/animation/quick-guide#animate-background

I often see examples like:

modifier = Modifier.drawBehind {
        drawRect(animatedColor)
    }

But what if I have an icon? In the example below, the number of recompositions will be about 11 in Recomposition Counts Tool, and drawRect() will won't work as I expect, because a rectangle with the specified color will simply be drawn behind the icon, but will not change the color of the icon itself. How can I reduce the number of recompositions in this situation?

val isClicked = remember {
        mutableStateOf(false)
    }
val color = animateColorAsState(targetValue = if (isClicked.value) Color.White else Color.Blue)

Icon(
    modifier = Modifier.clickable {
        isClicked.value = !isClicked.value
    },
    imageVector = Icons.Outlined.Home,
    contentDescription = null,
    tint = color.value,
)

Solution

  • You can get painter with rememberPainter(imageVector) and DrawScope has an extension function for painter. Since we animate in drawing phase you will only have one recomposition.

    Also you can either set a fixed size or get painter size in dp with its intrinsic size which is size of drawable itself.

    @Preview
    @Composable
    private fun ImageVectorAnimationSample() {
        val isClicked = remember {
            mutableStateOf(false)
        }
        val color by animateColorAsState(
            targetValue = if (isClicked.value) Color.White else Color.Blue,
            animationSpec = tween(durationMillis = 2000),
            label = "tint"
        )
    
        val painter = rememberVectorPainter(Icons.Outlined.Home)
    
        val dpSize = with(LocalDensity.current) {
            painter.intrinsicSize.toDpSize()
        }
    
        SideEffect {
            println("Composing...")
        }
        Box(
            modifier = Modifier
    //            .size(dpSize)
                .size(100.dp)
                .drawBehind {
                    with(painter) {
                        draw(size, colorFilter = ColorFilter.tint(color))
                    }
                }
                .clickable {
                    isClicked.value = !isClicked.value
                }
        )
    }