Jetpack Compose拖拽Composable:图片拖拽后位置更新异常
Jetpack Compose图片拖拽释放后位置更新异常问题
我参考常见场景实现,尝试在Jetpack Compose中实现图片拖拽移动功能,当前是用Fragment承载Compose内容的遗留应用,但这和问题无关。
代码实现
class DAndDFragment : Fragment() { private val viewModel by viewModels<DAndDViewModel>() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { return ComposeView(requireContext()).apply { setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) setContent { val pip by viewModel.pip.collectAsState() AppTheme { DragAndDrop(modifier = Modifier.fillMaxSize()) { Dragged( modifier = Modifier.offset( pip.pos.x.dp, pip.pos.y.dp ), onDropped = { position -> viewModel.pos(position.x, position.y) } ) { Box(modifier = Modifier.size(pip.size.dp)) { Image( painter = rememberAsyncImagePainter(pip.image), contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier .size(pip.size.dp) .clip(appRoundedCornerShape()) ) } } } } } } } @Composable fun Dragged( modifier: Modifier = Modifier, onDropped: (Offset) -> Unit, content: @Composable (Boolean) -> Unit ) { var currentPosition by remember { mutableStateOf(Offset.Zero) } val currentState = LocalDragTargetInfo.current Box(modifier = modifier .onGloballyPositioned { currentPosition = it.localToWindow(Offset.Zero) } .pointerInput(Unit) { detectDragGesturesAfterLongPress(onDragStart = { currentState.isDragging = true currentState.position = currentPosition + it currentState.draggableComposable = content }, onDrag = { change, dragAmount -> change.consume() currentState.offset += dragAmount }, onDragEnd = { currentState.isDragging = false currentState.position += currentState.offset onDropped(currentState.position) currentState.offset = Offset.Zero }, onDragCancel = { currentState.offset = Offset.Zero currentState.isDragging = false }) }) { content(currentState.isDragging) } } } internal class DragTargetInfo { var isDragging: Boolean by mutableStateOf(false) var position: Offset by mutableStateOf(Offset(0f, 0f)) var offset: Offset by mutableStateOf(Offset.Zero) var draggableComposable by mutableStateOf<(@Composable (Boolean) -> Unit)>({ }) } internal val LocalDragTargetInfo = compositionLocalOf { DragTargetInfo() } @Composable fun DragAndDrop( modifier: Modifier = Modifier, content: @Composable BoxScope.() -> Unit ) { val state = remember { DragTargetInfo() } CompositionLocalProvider( LocalDragTargetInfo provides state ) { Box(modifier = modifier.fillMaxSize()) { content() if (state.isDragging) { var targetSize by remember { mutableStateOf(IntSize.Zero) } Box(modifier = Modifier .graphicsLayer { val offset = state.position + state.offset alpha = if (targetSize == IntSize.Zero) 0f else .5f translationX = offset.x.minus(targetSize.width / 2) translationY = offset.y.minus(targetSize.height / 2) } .onGloballyPositioned { targetSize = it.size } ) { state.draggableComposable(true) } } } } }
问题描述
拖拽过程一切正常,但释放拖拽的图片时,应用触发onDragEnd回调后,通过currentState.position += currentState.offset计算的最终位置无法正确更新图片的实际位置,而同样的计算方式在DragAndDrop组件中显示拖拽中的图片时是正常的。请问这是什么原因?该如何解决?
问题原因与解决方案
原因分析
- 坐标体系不匹配:
currentPosition是通过localToWindow获取的窗口坐标系位置,但图片最终用Modifier.offset(x.dp, y.dp)设置的是父布局本地坐标系的偏移,两者基准点不同(窗口坐标系以屏幕左上角为原点,本地坐标系以父布局左上角为原点),直接传递窗口坐标会导致位置偏差。 - 预览与最终位置逻辑不一致:拖拽预览时通过
minus(targetSize.width/2)让元素居中显示,但最终位置计算未考虑这个偏移,导致释放后图片位置和预览位置不匹配。
解决方案
需要将窗口坐标转换为父布局本地坐标,并对齐预览逻辑的居中偏移,修改后的Dragged函数如下:
@Composable fun Dragged( modifier: Modifier = Modifier, onDropped: (Offset) -> Unit, content: @Composable (Boolean) -> Unit ) { val parentCoordinates = LocalLayoutCoordinates.current var currentPosition by remember { mutableStateOf(Offset.Zero) } val currentState = LocalDragTargetInfo.current var elementSize by remember { mutableStateOf(IntSize.Zero) } val density = LocalDensity.current Box(modifier = modifier .onGloballyPositioned { currentPosition = it.localToWindow(Offset.Zero) elementSize = it.size } .pointerInput(Unit) { detectDragGesturesAfterLongPress(onDragStart = { pressOffset -> currentState.isDragging = true currentState.position = currentPosition + pressOffset currentState.draggableComposable = content }, onDrag = { change, dragAmount -> change.consume() currentState.offset += dragAmount }, onDragEnd = { currentState.isDragging = false // 计算最终窗口位置 val finalWindowPosition = currentState.position + currentState.offset // 转换为父布局本地坐标系 val finalLocalPosition = parentCoordinates.windowToLocal(finalWindowPosition) // 减去元素半尺寸,和预览逻辑对齐 val adjustedPosition = finalLocalPosition - Offset( elementSize.width / 2f, elementSize.height / 2f ) // 转换为dp单位,匹配ViewModel存储的格式 val dpPosition = with(density) { Offset(adjustedPosition.x.toDp().value, adjustedPosition.y.toDp().value) } onDropped(dpPosition) currentState.offset = Offset.Zero }, onDragCancel = { currentState.offset = Offset.Zero currentState.isDragging = false }) ) { content(currentState.isDragging) } }
关键修改说明
- 使用
LocalLayoutCoordinates.current将窗口坐标转换为父布局本地坐标,确保和Modifier.offset的坐标系一致。 - 减去元素半尺寸,让最终位置和拖拽预览的视觉位置对齐,避免释放后图片跳变。
- 将像素坐标转换为dp单位,匹配你ViewModel中存储的位置格式,确保
Modifier.offset(x.dp, y.dp)能正确生效。
内容的提问来源于stack exchange,提问作者kingston
相关产品推荐
相关产品推荐

