Compose 中的拖放

1. 开始之前

本 Codelab 提供了有关实现 Compose 拖放操作基础知识的实用指导。您将学习如何启用在应用内以及跨不同应用拖放视图。您将学习如何在应用内甚至跨不同应用实现拖放操作。

先决条件

要完成本 Codelab,您需要

您将执行的操作

创建一个简单的应用,该应用可以

  • 使用 dragAndDropSource 修饰符配置可组合项以使其可拖动
  • 使用 dragAndDropTarget 修饰符配置可组合项以使其成为放置目标
  • 使用 Compose 接收丰富内容

您需要什么

2. 拖放事件

一个 拖放操作 可以被视为一个包含 4 个阶段的事件,这些阶段分别是

  1. 开始:系统响应用户的拖动手势开始拖放操作。
  2. 继续:用户继续拖动。
  3. 结束:用户在放置目标可组合项中释放拖动。
  4. 退出:系统发送信号以结束拖放操作。

系统在 DragEvent 对象中发送拖动事件。DragEvent 对象可以包含以下数据

  1. ActionType:基于拖放事件的生命周期事件的事件操作值。例如 ACTION_DRAG_STARTEDACTION_DROP 等。
  2. ClipData:正在拖动的包裹在 ClipData 对象中的数据。
  3. ClipDescription:有关 ClipData 对象的元信息。
  4. Result:拖放操作的结果。
  5. X:拖动对象的当前位置的 x 坐标。
  6. Y:拖动对象的当前位置的 y 坐标。

3. 设置

创建一个新项目并选择“空活动”模板。

19da275afd995463.png

将所有参数保留为默认值。

在本 Codelab 中,我们将使用 ImageView 演示拖放功能。让我们为 compose 的 glide 库添加一个 gradle 依赖项并同步项目。

implementation("com.github.bumptech.glide:compose:1.0.0-beta01")

现在在 MainActivity.kt 中,为 Image 创建一个 composable,它将作为我们的拖动源。

@Composable
fun DragImage(url: String) {
   GlideImage(model = url, contentDescription = "Dragged Image")
}

类似地,创建放置目标图像。

@Composable
fun DropTargetImage(url: String) {
   val urlState = remember {mutableStateOf(url)}
   GlideImage(model = urlState.value, contentDescription = "Dropped Image")
}

向您的可组合项添加一个列可组合项以包含这两个图像。

Column {
   DragImage(url = getString(R.string.source_url))
   DropTargetImage(url = getString(R.string.target_url))
}

在此阶段,我们有 MainActivity 以垂直方式显示两个图像。您应该能够看到此屏幕。

5e12c26cb2ad1068.png

4. 配置拖动源

让我们为我们的 DragImage 可组合项添加一个拖放源修饰符。

modifier = Modifier.dragAndDropSource {
   detectTapGestures(
       onLongPress = {
           startTransfer(
               DragAndDropTransferData(
                   ClipData.newPlainText("image uri", url)
               )
           )
       }
   )
}

在这里,我们添加了一个 dragAndDropSource 修饰符。dragAndDropSource 修饰符为应用它的任何元素启用拖放功能。它以拖动阴影的形式直观地表示拖动的元素。

dragAndDropSource 修饰符提供 PointerInputScope 来检测拖动手势。我们使用了 detectTapGesture PointerInputScope 来检测长按,这是我们的拖动手势。

onLongPress 方法正在启动正在拖动的数据的传输。

startTransfer 使用 transferData 作为手势完成后要传输的数据启动拖放会话。它获取封装在 DragAndDropTransferData 中的数据,该数据具有 3 个字段

  1. Clipdata: 要传输的实际数据。
  2. flags:控制拖放操作的标志。
  3. localState:在同一活动中拖动时的会话本地状态。

ClipData 是一个复杂的对象,它包含不同类型的项目,包括文本、标记、音频、视频等。在本 Codelab 中,我们将 imageurl 用作 ClipData 中的一个项目。

太好了,现在我们的视图可以拖动了!

415dcef002492e61.gif

5. 配置放置目标

要使视图能够接受放置的项目,它应该添加 dragAndDropTarget 修饰符

Modifier.dragAndDropTarget(
   shouldStartDragAndDrop = {
       // condition to accept dragged item
   },
   target = // DragAndDropTarget
   )
)

dragAndDropTarget 是允许将数据拖动到可组合项中的修饰符。此修饰符有两个参数

  1. shouldStartDragAndDrop:允许可组合项通过检查启动会话的 DragAndDropEvent 来决定是否希望从给定的拖放会话中接收。
  2. target:将接收给定拖放会话的事件的 DragAndDropTarget。

让我们添加一个条件,当我们想要将拖动事件传递给 DragAndDropTarget 时。

shouldStartDragAndDrop = { event ->
   event.mimeTypes()
       .contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
}

此处添加的条件是仅在正在拖动的项目中至少有一个为纯文本时才允许放置操作。如果没有任何项目是纯文本,则放置目标将不会被激活。

对于 target 参数,让我们创建一个 DragAndDropTarget 对象,它处理放置会话。

val dndTarget = remember{
   object : DragAndDropTarget{
       // handle Drag event
   }
}

DragAndDropTarget 具有一个回调,可在拖放会话的每个阶段覆盖。

  1. onDrop 一个项目已在此 DragAndDropTarget 内放置,返回 true 表示已使用 DragAndDropEvent;false 表示已拒绝。
  2. onStarted 拖放会话刚刚开始,并且此 DragAndDropTarget 有资格接收它。这提供了一个机会,可以在准备使用拖放会话时为 DragAndDropTarget 设置状态。
  3. onEntered 正在放置的项目已进入此 DragAndDropTarget 的边界。
  4. onMoved 正在放置的项目已在此 DragAndDropTarget 的边界内移动。
  5. onExited 正在放置的项目已移出此 DragAndDropTarget 的边界。
  6. onChanged 当前拖放会话中的事件已在 DragAndDropTarget 边界内更改。可能已按下或释放了修饰键。
  7. onEnded 拖放会话已完成。之前收到 onStarted 事件的层次结构中的所有 DragAndDropTarget 实例都将收到此事件。这提供了一个机会来重置 DragAndDropTarget 的状态。

让我们定义当项目放置在目标可组合项中时会发生什么。

override fun onDrop(event: DragAndDropEvent): Boolean {
   val draggedData = event.toAndroidDragEvent().clipData.getItemAt(0).text
   urlState.value = draggedData.toString()
   return true
}

onDrop 函数中,我们正在提取 ClipData 项目并将其分配给图像 url,并返回 true 以表示放置已正确处理。

让我们现在将此 DragAndDropTarget 实例插入到 dragAndDropTarget 修饰符的 target 参数中。

Modifier.dragAndDropTarget(
   shouldStartDragAndDrop = { event ->
       event.mimeTypes()
           .contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
   },
   target = dndTarget
)

太棒了,我们现在可以成功执行拖放操作了!

277ed56f80460dec.gif

虽然我们添加了拖放功能,但从视觉上很难理解发生了什么。让我们改变这一点。

对于放置目标可组合项,让我们将 ColorFilter 应用到我们的图像。

var tintColor by remember {
   mutableStateOf(Color(0xffE5E4E2))
}

定义色调颜色后,让我们将 ColorFilter 添加到我们的图像。

GlideImage(
   colorFilter = ColorFilter.tint(color = backgroundColor,
       blendMode = BlendMode.Modulate),
   // other params
)

当拖动的项目进入放置目标区域时,我们希望将色调颜色应用到图像。为此,我们可以覆盖 onEntered 回调。

override fun onEntered(event: DragAndDropEvent) {
   super.onEntered(event)
   tintColor = Color(0xff00ff00)
}

同样,当用户从目标区域拖出时,我们应该回退到原始颜色滤镜。为此,我们必须覆盖 onExited 回调。

override fun onExited(event: DragAndDropEvent) {
   super.onEntered(event)
   tintColor = Color(0xffE5E4E2)
}

在拖放成功完成后,我们也可以恢复到原始 ColorFilter

override fun onEnded(event: DragAndDropEvent) {
   super.onEntered(event)
   tintColor = Color(0xffE5E4E2)
}

最后,我们的放置目标可组合项如下所示

@Composable
fun DropTargetImage(url: String) {
   val urlState = remember {
       mutableStateOf(url)
   }
   var tintColor by remember {
       mutableStateOf(Color(0xffE5E4E2))
   }
   val dndTarget = remember {
       object : DragAndDropTarget {
           override fun onDrop(event: DragAndDropEvent): Boolean {
               val draggedData = event.toAndroidDragEvent()
                   .clipData.getItemAt(0).text
               urlState.value = draggedData.toString()
               return true
           }

           override fun onEntered(event: DragAndDropEvent) {
               super.onEntered(event)
               tintColor = Color(0xff00ff00)
           }
           override fun onEnded(event: DragAndDropEvent) {
               super.onEntered(event)
               tintColor = Color(0xffE5E4E2)
           }
           override fun onExited(event: DragAndDropEvent) {
               super.onEntered(event)
               tintColor = Color(0xffE5E4E2)
           }

       }
   }
   GlideImage(
       model = urlState.value,
       contentDescription = "Dropped Image",
       colorFilter = ColorFilter.tint(color = tintColor,
           blendMode = BlendMode.Modulate),
       modifier = Modifier
           .dragAndDropTarget(
               shouldStartDragAndDrop = { event ->
                   event
                       .mimeTypes()
                       .contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
               },
               target = dndTarget
           )
   )
}

太棒了,我们能够为拖放操作添加视觉提示!

6be7e749d53d3e7e.gif

6. 祝贺你!

Compose 的拖放 提供了一个简单的界面,可以使用视图的修饰符在 Compose 中实现拖放功能。

总而言之,您已学习使用 compose 实现拖放。请进一步探索文档。

了解更多