October 27, 2024
Chicago 12, Melborne City, USA
Android

How to make animation on LazyGrid for Item perform only first time appearence?


For some reason there are 2 recompostions happens which results to unable to set a condition for the item to check if it has been already presented to a user.

I would like to make such an animation for LazyGrid (I tried to optimize my code a little bit, but the meaning the same) – https://yasinkacmaz.medium.com/simple-item-animation-with-jetpack-composes-lazygrid-78316992af22
Make an items to appear like bubble effect

There is my code:

private val dataSet: List<String> = listOf("Item 1", "Item 2", "Item 3", "Item 4", "Item 5")
private val data: List<String> = List(5) { dataSet }.flatten()

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            Test_delete_itTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Gallery(
                        paddingValues = innerPadding,
                        uiConfig = { data }
                    )
                }
            }
        }
    }
}

@Composable
private fun Gallery(
    paddingValues: PaddingValues,
    uiConfig: () -> List<String>
) {
    val config: List<String> = uiConfig()
    val columns = 2

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
    ) {
        LazyVerticalGrid(
            columns = GridCells.Fixed(columns),
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            content = {
                items(config.size) { idx ->
                    val item: String = config[idx]
                    val (scale, alpha) = scaleAndAlpha(idx, columns)

                    MyItem(
                        modifier = Modifier.graphicsLayer(alpha = alpha, scaleX = scale, scaleY = scale),
                        text = item
                    )
                }
            }
        )
    }
}

@Composable
private fun MyItem(
    modifier: Modifier = Modifier,
    text: String
) {
    Card(
        modifier = modifier.height(150.dp),
        shape = RoundedCornerShape(16.dp),
        elevation = CardDefaults.cardElevation(8.dp),
        colors = CardDefaults.cardColors(
            containerColor = Color.Blue,
        )
    ) {
        Box(
            modifier = Modifier
                .weight(1f)
                .height(150.dp)
                .clip(RoundedCornerShape(16.dp))
        ) {
            Text(
                text = text,
                color = Color.White
            )
        }
    }
}

@Immutable
private enum class State { PLACING, PLACED }

@Immutable
data class ScaleAndAlphaArgs(
    val fromScale: Float,
    val toScale: Float,
    val fromAlpha: Float,
    val toAlpha: Float
)

@OptIn(ExperimentalTransitionApi::class)
@Composable
fun scaleAndAlpha(
    args: ScaleAndAlphaArgs,
    animation: FiniteAnimationSpec<Float>
): Pair<Float, Float> {
    val transitionState = remember { MutableTransitionState(State.PLACING).apply { targetState = State.PLACED } }
    val transition = rememberTransition(transitionState, label = "")
    val alpha by transition.animateFloat(transitionSpec = { animation }, label = "") {
        if (it == State.PLACING) args.fromAlpha else args.toAlpha
    }
    val scale by transition.animateFloat(transitionSpec = { animation }, label = "") {
        if (it == State.PLACING) args.fromScale else args.toScale
    }
    return alpha to scale
}

val scaleAndAlpha: @Composable (idx: Int, columns: Int) -> Pair<Float, Float> = { idx, columns ->
    scaleAndAlpha(
        args = ScaleAndAlphaArgs(2f, 1f, 0f, 1f),
        animation = tween(300, delayMillis = (idx / columns) * 100)
    )
}

I tried to add a condition for the first time presented:

@Composable
private fun Gallery(
    paddingValues: PaddingValues,
    uiConfig: () -> List<String>
) {
    val config: List<String> = uiConfig()
    val columns = 2

    // Remember a set of already animated indices
    val animatedIndices = remember { mutableSetOf<Int>() }

    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(paddingValues)
    ) {
        LazyVerticalGrid(
            columns = GridCells.Fixed(columns),
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.spacedBy(8.dp),
            horizontalArrangement = Arrangement.spacedBy(8.dp),
            content = {
                items(config.size) { idx ->
                    val item: String = config[idx]

                    // Determine if the item should animate
                    val shouldAnimate = !animatedIndices.contains(idx)

                    // If it should animate, mark it as animated
                    if (shouldAnimate) {
                        animatedIndices.add(idx)
                    }

                    val (scale, alpha) = if (shouldAnimate) {
                        scaleAndAlpha(idx, columns)
                    } else {
                        1f to 1f // No animation
                    }

                    MyItem(
                        modifier = Modifier.graphicsLayer(alpha = alpha, scaleX = scale, scaleY = scale),
                        text = item
                    )
                }
            }
        )
    }
}

But the issue is that recomposition happens twice here – items(config.size) { idx -> that makes the condition useless.

What am I missing here?



You need to sign in to view this answers

Leave feedback about this

  • Quality
  • Price
  • Service

PROS

+
Add Field

CONS

+
Add Field
Choose Image
Choose Video