fix(photo-editor): 修复裁剪框拖动的闭包快照陷阱
pointerInput(Unit) 的手势 lambda 只在首次组合捕获一次 state, 后续 onDragStart 读的是过期快照,导致再次拖动从中心开始。 改用本地累积值绕开 StateFlow 往返:onDragStart 把 state 快照到 mutableFloatStateOf,onDrag 每帧在累积值上叠加增量,onDragEnd 一次性提交。onDragStart 通过 rememberUpdatedState 读最新 state。 enterCrop 改为幂等(已启用则保留当前框),避免 LaunchedEffect 因 editorState 变化重触时冲掉用户拖动结果。
This commit is contained in:
parent
b8509381d9
commit
d3ebd6829b
@ -80,10 +80,11 @@ class PhotoEditorViewModel(
|
|||||||
update { it.copy(rotationDegrees = it.rotationDegrees + delta) }
|
update { it.copy(rotationDegrees = it.rotationDegrees + delta) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 进入裁剪模式:启用裁剪框并重置为默认居中区域。 */
|
/** 进入裁剪模式:仅在尚未启用时初始化为默认居中区域,已启用则保留当前框。 */
|
||||||
fun enterCrop() {
|
fun enterCrop() {
|
||||||
update {
|
update {
|
||||||
it.copy(cropLeft = 0.1f, cropTop = 0.1f, cropRight = 0.9f, cropBottom = 0.9f)
|
if (it.cropEnabled) it
|
||||||
|
else it.copy(cropLeft = 0.1f, cropTop = 0.1f, cropRight = 0.9f, cropBottom = 0.9f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -40,8 +40,10 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
@ -436,12 +438,22 @@ private fun CropOverlay(
|
|||||||
onCropChange: (Float, Float, Float, Float) -> Unit,
|
onCropChange: (Float, Float, Float, Float) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
// 拖动手柄内聚在此组件,避免跨重组延迟
|
|
||||||
var dragHandle by remember { mutableStateOf(CropHandle.NONE) }
|
var dragHandle by remember { mutableStateOf(CropHandle.NONE) }
|
||||||
val left = state.cropLeft ?: 0.1f
|
// 拖动期间的本地累积值;非拖动时为 null,绘制回退到 state
|
||||||
val top = state.cropTop
|
var dragLeft by remember { mutableFloatStateOf(0f) }
|
||||||
val right = state.cropRight ?: 0.9f
|
var dragTop by remember { mutableFloatStateOf(0f) }
|
||||||
val bottom = state.cropBottom
|
var dragRight by remember { mutableFloatStateOf(0f) }
|
||||||
|
var dragBottom by remember { mutableFloatStateOf(0f) }
|
||||||
|
// pointerInput(Unit) 只捕获一次 state,用 rememberUpdatedState 让
|
||||||
|
// 手势 lambda 内始终读到最新的 state(含上次拖动提交后的值)
|
||||||
|
val currentState by rememberUpdatedState(state)
|
||||||
|
|
||||||
|
// 绘制用值:拖动中取本地累积,否则取 state
|
||||||
|
val left = if (dragHandle != CropHandle.NONE) dragLeft else (state.cropLeft ?: 0.1f)
|
||||||
|
val top = if (dragHandle != CropHandle.NONE) dragTop else state.cropTop
|
||||||
|
val right = if (dragHandle != CropHandle.NONE) dragRight else (state.cropRight ?: 0.9f)
|
||||||
|
val bottom = if (dragHandle != CropHandle.NONE) dragBottom else state.cropBottom
|
||||||
|
|
||||||
Canvas(
|
Canvas(
|
||||||
modifier = modifier.pointerInput(Unit) {
|
modifier = modifier.pointerInput(Unit) {
|
||||||
detectDragGestures(
|
detectDragGestures(
|
||||||
@ -449,13 +461,16 @@ private fun CropOverlay(
|
|||||||
val size = this.size
|
val size = this.size
|
||||||
val ox = offset.x / size.width
|
val ox = offset.x / size.width
|
||||||
val oy = offset.y / size.height
|
val oy = offset.y / size.height
|
||||||
dragHandle = hitHandle(
|
val baseL = currentState.cropLeft ?: 0.1f
|
||||||
ox, oy,
|
val baseT = currentState.cropTop
|
||||||
state.cropLeft ?: 0.1f,
|
val baseR = currentState.cropRight ?: 0.9f
|
||||||
state.cropTop,
|
val baseB = currentState.cropBottom
|
||||||
state.cropRight ?: 0.9f,
|
// 进入拖动:把 state 当前值快照到本地累积
|
||||||
state.cropBottom
|
dragLeft = baseL
|
||||||
)
|
dragTop = baseT
|
||||||
|
dragRight = baseR
|
||||||
|
dragBottom = baseB
|
||||||
|
dragHandle = hitHandle(ox, oy, baseL, baseT, baseR, baseB)
|
||||||
},
|
},
|
||||||
onDrag = { change, drag ->
|
onDrag = { change, drag ->
|
||||||
if (dragHandle == CropHandle.NONE) return@detectDragGestures
|
if (dragHandle == CropHandle.NONE) return@detectDragGestures
|
||||||
@ -463,18 +478,20 @@ private fun CropOverlay(
|
|||||||
val size = this.size
|
val size = this.size
|
||||||
val dx = drag.x / size.width
|
val dx = drag.x / size.width
|
||||||
val dy = drag.y / size.height
|
val dy = drag.y / size.height
|
||||||
// 每帧从 state 读最新值,叠加增量
|
// 在本地累积值上叠加增量,不触发 StateFlow 往返
|
||||||
val cur = moveCrop(
|
val cur = moveCrop(dx, dy, dragLeft, dragTop, dragRight, dragBottom, dragHandle)
|
||||||
dx, dy,
|
dragLeft = cur.left
|
||||||
state.cropLeft ?: 0.1f,
|
dragTop = cur.top
|
||||||
state.cropTop,
|
dragRight = cur.right
|
||||||
state.cropRight ?: 0.9f,
|
dragBottom = cur.bottom
|
||||||
state.cropBottom,
|
},
|
||||||
dragHandle
|
onDragEnd = {
|
||||||
)
|
// 拖动结束:一次性提交回 ViewModel
|
||||||
onCropChange(cur.left, cur.top, cur.right, cur.bottom)
|
if (dragHandle != CropHandle.NONE) {
|
||||||
|
onCropChange(dragLeft, dragTop, dragRight, dragBottom)
|
||||||
|
}
|
||||||
|
dragHandle = CropHandle.NONE
|
||||||
},
|
},
|
||||||
onDragEnd = { dragHandle = CropHandle.NONE },
|
|
||||||
onDragCancel = { dragHandle = CropHandle.NONE }
|
onDragCancel = { dragHandle = CropHandle.NONE }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user