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

2.9k views Asked by At

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
                                                    }
1

There are 1 answers

3
Phil Dukhov On BEST ANSWER

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)
            }
        }
    }
}