当我在不同Android API级别的设备上运行问题末尾的代码时,应用程序的行为有所不同.

Device 1: Android API 33 (Android 13)
The code works as expected. The value of camera2D is changed and the composable is recomposed.

Device 2: Android API 26 (Android Oreo)
The code does not work as expected. The value of camera2D is changed but the composable is NOT recomposed.

代码:

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

推荐答案

当闭包捕获State,而当您创建该State的新实例时,新的实例不会传递给闭包时,就会出现这个问题.

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

创建MutableState的新实例,但pointerInput内存在以前的实例.

您需要将相同的关键点设置为pointerInput(key1 = maxWidth, key2 = maxHeight)才能将其重置,以便在重置记住后,它将捕捉指定给camera2D的当前MutableState.

详细解释了引擎盖下的LaunchedEffectDisposableEffect或两者都是remember,或者pointerInput如何捕获State,并且除非您用keys重置它们,否则不会得到新的State.

Value of MutableState inside Modifier.pointerInput doesn't change after remember keys updated

https://stackoverflow.com/a/73610519/5457853

另外,请判断这个答案的最后一部分,您可以看到新实例没有存储在LaunchedEffect的lambda中.

https://stackoverflow.com/a/77321291/5457853

Android相关问答推荐

在Android Studio中陷入了不兼容的Gradle版本的循环

无法从API访问项目详细信息

Android和Rust,OpenSSL交叉编译在ARM V7上链接失败

保护所有程序包文件和类

如何更新Kotlin中的显示?

Play Google上发布的一款应用的房间数据库迁移

第一次使用onBackPressed()、NavigateUp()加载时MapView崩溃

我们可以使用KSP读取类中变量的值吗?

使用 async 向网络发出并行请求并在supervisorScope中处理它们

错误:参数的类型必须是用@Entity注释的类或其集合/array. java.lang.String tocd);

如何在Android Studio中删除项目

Android WebView 不会在滚动端加载新内容

Jetpack Compose 绘制范围内的动画

Android 设备断开连接后发送的 BLE 蓝牙数据

任务 ':app:checkReleaseDuplicateClasses' 执行失败

导航组件中预期的函数调用map(...)

如何在 Android Jetpack compose 中为列表初始填充设置动画

记住或不记得derivedStateOf

在 android list 中添加 IsMonitoringTool 元数据标志的位置

更新应用程序是否会取消对应用程序特定文件的权限?