You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

LaunchedEffect异常:Compose标记动画无法获取正确偏移值

问题:Compose中Flow更新与LaunchedEffect读取markersMap值不一致导致动画异常

问题背景代码

屏幕Composable(接收Flow并更新markersMap)

@Composable
fun Screen() {
    val receivedFlow = viewModel.receiverFlow.collectAsStateWithLifecycle(
        initialValue = null,
        minActiveState = Lifecycle.State.RESUMED
    )

    receivedFlow.value?.let { received ->
        markersMap[received.stationId]?.offset =  markersMap[received.stationId]?.nextOffset
        markersMap[received.stationId]?.nextOffset = Offset(received.x, received.y)
    }
}

动画Composable(原方案:定时轮询执行动画)

@Composable
fun NativeMarkersAnimator() {
    val offsetsMap = remember {
        mutableStateMapOf<Long, Animatable<Offset,AnimationVector2D>>()
    }

    LaunchedEffect(Unit) {
        while (true) {
            markersMap.forEach { marker ->
                val targetOffset = marker.value.nextOffset
                targetOffset?.let { target ->
                    offsetsMap[marker.value.stationId] =
                        Animatable(marker.value.offset, Offset.VectorConverter)
                    offsetsMap[marker.value.stationId]?.animateTo(target, tween(900))
                }
            }
            delay(1000)
        }
    }

    Canvas(modifier = Modifier.fillMaxSize()) {
        offsetsMap.forEach { offset ->
            val image = markersMap[offset.key]?.bitmap?.asImageBitmap()
            image?.let {
                drawImage(
                    image = image,
                    topLeft = offset.value.value
                )
            }
        }
    }
}

遇到的问题

Flow传来新的Offset值(如Offset(400,300))并更新到markersMap对应条目后,LaunchedEffect的循环中读取同一条目时得到的却是旧值或随机值(如Offset(600,600)),即使Flow更新多次,LaunchedEffect内的值仍无变化,导致动画重复从错误的初始偏移点执行相同路径。

解决方法(编辑后可行代码)

将动画逻辑直接绑定到Flow的每一次数据接收处理中:

receivedFlow.value?.let { received ->
    coroutineScope.launch {
        withContext(Dispatchers.Default) {
            handleDenmAlertData(received.denmAlertData, viewModel)

            nativeAnimationManager?.let { nativeAnimationManager ->
                val map = nativeAnimationManager.mapboxMap
                val point = Point.fromLngLat(received.lastLongitude, received.lastLatitude)
                val projected = map.pixelForCoordinate(point)

                if (!markersMap.containsKey(received.stationId)) {
                    markersMap[received.stationId] = RoadUserMarker(
                        stationId = received.stationId,
                        prevOffset = Offset(
                            x = projected.x.toFloat(),
                            y = projected.y.toFloat()
                        ),
                        point = point,
                        stationType = received.stationType,
                        bitmap = findBitmap(received),
                        nextOffset = null,
                        animatedOffset = null,
                        lastUpdateTime = System.currentTimeMillis()
                    )
                } else {
                    markersMap[received.stationId]?.let { marker ->
                        marker.nextOffset?.let {
                            marker.prevOffset = it
                            println("Current offset***: " + markersMap[received.stationId]?.prevOffset)
                        }
                        marker.nextOffset = Offset(projected.x.toFloat(), projected.y.toFloat())
                        marker.animatedOffset = Animatable(marker.prevOffset, Offset.VectorConverter)
                        marker.animatedOffset!!.animateTo(marker.nextOffset!!, tween(900))
                    }
                }
            }
        }
    }

    Canvas(modifier = Modifier.fillMaxSize()) {
        markersMap.forEach { markerEntry ->
            val image = markerEntry.value.bitmap.asImageBitmap()
            markerEntry.value.animatedOffset?.let {
                drawImage(
                    image = image,
                    topLeft = it.value
                )
            }
        }
    }

关键原因与提示

  1. 状态容器类型错误:如果markersMap是普通HashMap而非Compose提供的mutableStateMapOf,它不具备状态感知能力。LaunchedEffect(Unit)只会初始化一次,内部循环捕获的是map的旧引用,无法感知后续更新。
  2. 异步逻辑不同步:原方案中Flow更新与LaunchedEffect轮询是两个独立协程,没有同步机制,当map非线程安全时,极易出现读写不一致。
  3. 响应式逻辑不匹配:Compose是响应式框架,定时轮询的方式违背了"数据驱动UI"的原则。将动画触发绑定到数据更新时机,能从根源保证数据与动画的一致性。

内容的提问来源于stack exchange,提问作者Jolly Wojak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 19:02:05