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

Android API26(Oreo)下修改MutableState后Composable不重组问题

不同Android API级别下Compose重组异常问题

现象差异

  • 设备1:Android API 33(Android 13)
    代码运行符合预期,camera2D的值被修改后,Composable会触发重组。

  • 设备2:Android API 26(Android Oreo)
    代码运行不符合预期,camera2D的值已被修改,但Composable未触发重组。

复现代码

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.calculatePan
import androidx.compose.foundation.gestures.calculateZoom
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.withTransform
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp

@Composable
fun EditorScreen() {
    BoxWithConstraints {
        val dpToPxRatio = with(LocalDensity.current) {
            1.dp.toPx()
        }

        val camera2D = remember(key1 = maxWidth, key2 = maxHeight) {
            mutableStateOf(
                Camera2D(
                    translation = Translation2D(
                        x = (maxWidth.value * dpToPxRatio) / 2f,
                        y = (maxHeight.value * dpToPxRatio) / 2f
                    ),
                    scale = Scale2D(
                        scaleX = 1f,
                        scaleY = 1f
                    )
                )
            )
        }

        val editorObjects = remember {
            mutableStateListOf(
                SquareEditorObject2D(
                    translation = Translation2D(x = 0.0f, y = 0.0f),
                    size = Size2D(width = 350.0f, height = 580.0f),
                    scale = Scale2D(scaleX = 1.0f, scaleY = 1.0f),
                    rotation = Rotation2D(zRotationDegrees = 0f),
                    color = Color.Blue
                ),
                SquareEditorObject2D(
                    translation = Translation2D(x = 0.0f, y = 0.0f),
                    size = Size2D(width = 350.0f, height = 580.0f),
                    scale = Scale2D(scaleX = 1.0f, scaleY = 1.0f),
                    rotation = Rotation2D(zRotationDegrees = 45f),
                    color = Color.Green
                ),
                SquareEditorObject2D(
                    translation = Translation2D(x = 0.0f, y = 0.0f),
                    size = Size2D(width = 350.0f, height = 580.0f),
                    scale = Scale2D(scaleX = 1.0f, scaleY = 1.0f),
                    rotation = Rotation2D(zRotationDegrees = 135f),
                    color = Color.Red
                )
            )
        }
        Canvas(
            modifier = Modifier
                .fillMaxSize()
                .pointerInput(key1 = Unit) {
                    awaitEachGesture {
                        awaitFirstDown()

                        do {
                            val currentCamera2D = camera2D.value

                            val event = awaitPointerEvent()
                            val pointerCount = event.changes.count()
                            if (pointerCount == 1) {
                                val dragAmount = event.calculatePan()

                                camera2D.value = currentCamera2D.copy(
                                    translation = Translation2D(
                                        x = currentCamera2D.translation.x + dragAmount.x,
                                        y = currentCamera2D.translation.y + dragAmount.y
                                    )
                                )
                            } else if (pointerCount > 1) {
                                val eventZoom = event.calculateZoom()

                                camera2D.value = currentCamera2D.copy(
                                    scale = Scale2D(
                                        scaleX = currentCamera2D.scale.scaleX * eventZoom,
                                        scaleY = currentCamera2D.scale.scaleY * eventZoom
                                    )
                                )
                            }
                        } while (event.changes.any { it.pressed })
                    }
                }
        ) {
            withTransform(
                {
                    translate(
                        left = camera2D.value.translation.x,
                        top = camera2D.value.translation.y
                    )
                    scale(
                        scaleX = camera2D.value.scale.scaleX,
                        scaleY = camera2D.value.scale.scaleY
                    )
                }
            ) {
                for (editorObject in editorObjects) {
                    withTransform(
                        {
                            translate(
                                left = editorObject.translation.x - editorObject.pivot.x,
                                top = editorObject.translation.y - editorObject.pivot.y
                            )
                            rotate(
                                degrees = editorObject.rotation.zRotationDegrees,
                                pivot = Offset(
                                    x = editorObject.size.width / 2f,
                                    y = editorObject.size.height / 2f
                                )
                            )
                            scale(
                                scaleX = editorObject.scale.scaleX,
                                scaleY = editorObject.scale.scaleY,
                                pivot = Offset(
                                    x = editorObject.size.width / 2f,
                                    y = editorObject.size.height / 2f
                                )
                            )
                        }
                    ) {
                        drawRect(
                            color = editorObject.color,
                            size = Size(
                                width = editorObject.size.width,
                                height = editorObject.size.height
                            )
                        )
                    }
                }
            }
        }
    }
}

data class Translation2D(val x: Float, val y: Float)

data class Size2D(val width: Float, val height: Float)

data class Scale2D(val scaleX: Float, val scaleY: Float)

data class Rotation2D(val zRotationDegrees: Float)

data class SquareEditorObject2D(
    val translation: Translation2D = Translation2D(x = 0f, y = 0f),
    val size: Size2D,
    val scale: Scale2D = Scale2D(scaleX = 1f, scaleY = 1f),
    val rotation: Rotation2D = Rotation2D(zRotationDegrees = 0f),
    val color: Color
) {

    val pivot = Translation2D(
        x = (size.width / 2f),
        y = (size.height / 2f)
    )
}

data class Camera2D(
    val translation: Translation2D = Translation2D(x = 0f, y = 0f),
    val scale: Scale2D = Scale2D(scaleX = 1f, scaleY = 1f),
    val rotation: Rotation2D = Rotation2D(zRotationDegrees = 0f)
)

依赖版本

implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.7.2")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")

内容的提问来源于stack exchange,提问作者Augusto Carmo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 20:20:54