如何使 LazyGrid 上的 Item 动画仅在第一次出现时执行?

问题描述 投票:0回答:1

由于某种原因,发生了 2 次重新组合,导致无法为项目设置条件来检查它是否已呈现给用户。

我想为 LazyGrid 制作这样的动画(我尝试稍微优化一下我的代码,但含义相同) - https://yasinkacmaz.medium.com/simple-item-animation-with-jetpack-组成-lazygrid-78316992af22 让物品看起来像气泡效果

这是我的代码:

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

我第一次尝试添加一个条件:

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

但问题是重组在这里发生了两次 -

items(config.size) { idx ->
这使得条件变得毫无用处。

我在这里缺少什么?

android animation android-jetpack-compose android-animation
1个回答
0
投票

items
lambda 的第一个组合中,当
shouldAnimate
true
时,调用
scaleAndAlpha
。这是一个在动画的每一帧上重新组合的组合函数。它还会返回每次重组时
scale
alpha
的当前值。为了使
MyItem
能够相应更新,当
items
scale
发生变化时,整个
alpha
lambda 都会被重构。

这是您不希望拥有的第二个合成,因为现在

shouldAnimate
设置为
false
并且刚刚开始的动画被完全跳过。

一个简单的修复方法是将

scaleAndAlpha
MyItem
提取到专用的可组合项中,以便其重组独立于
shouldAnimate
:

@Composable
private fun MyAnimatedItem(
    shouldAnimate: Boolean,
    idx: Int,
    columns: Int,
    item: String,
) {
    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,
    )
}

简单地这样调用(另外我将其简化为使用

itemsIndexed
而不是
items
):

itemsIndexed(config) { idx, item ->
    // Determine if the item should animate
    val shouldAnimate = !animatedIndices.contains(idx)

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

    MyAnimatedItem(shouldAnimate, idx, columns, item)
}

现在,由于动画而发生的重组仅限于

MyAnimatedItem
itemsIndexed
lambda 不受影响,并且仅在项目滚出视口并再次滚入时才会重组。并且只有 then
shouldAnimate
按预期设置为
false

© www.soinside.com 2019 - 2024. All rights reserved.