首页 > 解决方案 > Jetpack 用图像奇怪的问题组成动画

问题描述

我看到 Jetpack compose 出现奇怪的图像闪烁问题。这是一个简单的双牌套牌,顶部图像在屏幕外显示动画以显示第二张牌。第二张卡显示正常一秒钟,然后第一张图像在屏幕上闪烁。我尝试过使用 Coil、Fresco 和 Glide,它们的行为方式都相同。

import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import coil.compose.rememberImagePainter
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.roundToInt

class MainViewModel : ViewModel() {
    var images = MutableLiveData(listOf(
        "https://images.pexels.com/photos/212286/pexels-photo-212286.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/163016/crash-test-collision-60-km-h-distraction-163016.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/1366944/pexels-photo-1366944.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/5878501/pexels-photo-5878501.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/3846022/pexels-photo-3846022.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    ))
}

class MainActivity : ComponentActivity() {
    private val model by viewModels<MainViewModel>()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            MainScreen(model)
        }
    }
}

@Composable
fun MainScreen(model: MainViewModel) {
    val images: List<String> by model.images.observeAsState(listOf())

    Box(
        modifier = Modifier.fillMaxSize()
    ) {
        images?.take(2).reversed().forEach {
            Card(url = it) {
                val d = model.images.value?.toMutableList()
                d?.let {
                    it.removeFirst()
                    model.images.value = it
                }
            }
        }
    }
}


@Composable
fun Card(
    url: String,
    advance: ()-> Unit = {},
){
    val coroutineScope = rememberCoroutineScope()
    var offsetX = remember(url) { Animatable(0f) }

    Box(
        modifier = Modifier
            .offset { IntOffset(offsetX.value.roundToInt(), 0) }
            .fillMaxSize()
            .background(color = Color.White)
            .clickable {
                coroutineScope.launch {
                    offsetX.animateTo(
                        targetValue = 3000F
                    )
                }
                coroutineScope.launch {
                    delay(400)
                    advance()
                }
            }
    ) {
        Image(
            painter = rememberImagePainter(
                data = url,
            ),
            contentDescription = null,
            modifier = Modifier
                .size(400.dp, 400.dp)
        )
    }
}

如果有人想尝试一下,也可以把它扔到 github 上: https ://github.com/studentjet/learncompose

标签: androidandroid-layoutandroid-jetpack-compose

解决方案


问题是这样的:你有两个卡片视图。删除最上面的卡片后,compose 会重新使用它们并使用新数据更新它们。虽然最上面的卡片加载了第二张图片,但它仍然显示了第一张。

您可以禁用缓存,但在这种情况下,图像仍会闪烁,因为它会首先显示一个空白空间。相反,您需要将第二张卡片的视图重用于第一张卡片。

特别是在这种情况下,key Composable 的用途是:即使顺序发生变化,它也会在重新组合之间重用相同 key 的内容。

val images = remember {
    mutableStateListOf(
        "https://images.pexels.com/photos/212286/pexels-photo-212286.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/163016/crash-test-collision-60-km-h-distraction-163016.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/1366944/pexels-photo-1366944.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/5878501/pexels-photo-5878501.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
        "https://images.pexels.com/photos/3846022/pexels-photo-3846022.jpeg?auto=compress&cs=tinysrgb&dpr=1&w=500",
    )
}

Box(
    modifier = Modifier.fillMaxSize()
) {
    images.take(2).reversed().forEach {
        key(it) {
            Card(url = it) {
                images.add(
                    images.removeFirst()
                )
            }
        }
    }
}


推荐阅读