Search code examples
androidandroid-jetpack-composelazycolumnjetpack-compose-animation

How to continuously animate text size based on lazy column / lazy llist scroll in Android - Jetpack Compose?


I want to make a smooth transition for scaling text inside the lazy column. Currently, I am using the graphics layer to animate the text scale based on the first visible item index from the list state. But it does not provide smooth and continuous animation. I want to make it as an Animated Flat list in React native. Here is an example of what I want to achieve.

Here is my code for scaling text based on the selected items.

 val animateSizeText by animateFloatAsState(
                                        targetValue = if (item == selectedItem) {
                                            1f
                                        }
                                        else if (item == selectedItem- 1 || item == selectedItem+ 1) {
                                            0.9f
                                        }
                                        else if (item == selectedItem- 2 || item == selectedItem+ 2) {
                                            0.7f
                                        }
                                        else {
                                            0.5f
                                        },
                                        animationSpec = tween(100, easing = LinearOutSlowInEasing)
                                    )

Modifier for scaling text:

                                             modifier = Modifier
                                                    .graphicsLayer {
                                                        scaleY = animateSizeText
                                                        scaleX = animateSizeText
                                                    }

Solution

  • Comparing to related question, you need to enable non default opacity value for other items using firstOrNull block and control how it depends on scroll position with a multiplier. It's pretty simple math, change this formula according to the scale effect you need.

    val items = remember {
        ('A'..'Z').map { it.toString() }
    }
    val listState = rememberLazyListState()
    val horizontalContentPadding = 16.dp
    val boxSize = 50.dp
    BoxWithConstraints {
        val halfRowWidth = constraints.maxWidth / 2
        LazyRow(
            state = listState,
            horizontalArrangement = Arrangement.spacedBy(16.dp),
            contentPadding = PaddingValues(horizontal = horizontalContentPadding, vertical = 8.dp),
            modifier = Modifier
                .fillMaxWidth()
        ) {
            itemsIndexed(items) { i, item ->
                val opacity by remember {
                    derivedStateOf {
                        val currentItemInfo = listState.layoutInfo.visibleItemsInfo
                            .firstOrNull { it.index == i }
                            ?: return@derivedStateOf 0.5f
                        val itemHalfSize = currentItemInfo.size / 2
                        (1f - minOf(1f, abs(currentItemInfo.offset + itemHalfSize - halfRowWidth).toFloat() / halfRowWidth) * 0.5f)
                    }
                }
                Box(
                    contentAlignment = Alignment.Center,
                    modifier = Modifier
                        .scale(opacity)
                        .alpha(opacity)
                        .size(boxSize)
                        .background(Color.Blue)
                ) {
                    Text(item, color = Color.White)
                }
            }
        }
    }