拖放 Codelab

1. 开始之前

此 Codelab 提供了有关实现视图拖放功能基础知识的实用说明。您将学习如何启用视图在您的应用内以及跨不同应用进行拖放。您将学习如何在您的应用内甚至跨不同应用实现拖放交互。此 Codelab 将指导您使用 DropHelper 启用拖放、使用 ShadowBuilder 自定义拖动期间的视觉反馈、添加跨应用拖动的权限以及实现普遍适用的内容接收器。

先决条件

要完成此 Codelab,您需要

您将执行的操作

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

  • 使用 DragStartHelperDropHelper 实现拖放功能
  • 更改 ShadowBuilder
  • 添加跨应用拖动的权限
  • 实现 Reach Content Receiver 以进行通用实现。

您需要的内容

2. 拖放事件

拖放过程可以被视为 4 个阶段的事件,这些阶段分别是

  1. 开始:系统响应用户的拖动手势开始拖放操作。
  2. 继续:用户继续拖动,拖动阴影构建器在进入目标视图时启动。
  3. 结束:用户在放置目标区域的边界框内释放拖动。
  4. 退出:系统发送信号以结束拖放操作。

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

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

3. 设置

创建一个新项目,并选择“空视图 Activity”模板

2fbd2bca1483033f.png

将所有参数保留为默认值。让项目同步并建立索引。您将看到 MainActivity.kt 以及视图 activity_main.xml 已创建。

4. 使用视图进行拖放

string.xml 中,让我们添加一些字符串值

<resources>
    <string name="app_name">DragAndDropCodelab</string>
    <string name="drag_image">Drag Image</string>
    <string name="drop_image">drop image</string>
 </resources>

打开 activity_main.xml 源文件,并修改布局以包含两个 ImageView,一个充当拖动源,另一个充当放置目标。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv_greeting"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toTopOf="@id/iv_source"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/iv_source"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/drag_image"
        app:layout_constraintBottom_toTopOf="@id/iv_target"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tv_greeting" />

    <ImageView
        android:id="@+id/iv_target"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/drop_image"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>

build.gradle.kts 中,启用视图绑定

buildFeatures{
   viewBinding = true
}

build.gradle.kts 中,添加 Glide 的依赖项

dependencies {
    implementation("com.github.bumptech.glide:glide:4.16.0")
    annotationProcessor("com.github.bumptech.glide:compiler:4.16.0")
    
    //other dependencies
}

在 string.xml 中添加图像网址和问候语文本

<string name="greeting">Drag and Drop</string>
<string name="target_url">https://services.google.com/fh/files/misc/qq2.jpeg</string>
<string name="source_url">https://services.google.com/fh/files/misc/qq10.jpeg</string>

MainActivity.kt 中,让我们初始化视图。

class MainActivity : AppCompatActivity() {
   val binding by lazy(LazyThreadSafetyMode.NONE) {
       ActivityMainBinding.inflate(layoutInflater)
   }

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(binding.root)
       binding.tvGreeting.text = getString(R.string.greeting)
       Glide.with(this).asBitmap()
           .load(getString(R.string.source_url))
           .into(binding.ivSource)
       Glide.with(this).asBitmap()
           .load(getString(R.string.target_url))
           .into(binding.ivTarget)
   }
}

此时,您的应用应显示问候语文本以及垂直方向的两个图像。

b0e651aaee336750.png

5. 使视图可拖动

要使特定视图可拖动,该视图必须在拖动手势上实现 startDragAndDrop() 方法。

让我们为 onLongClickListener 实现一个回调,因为用户在视图上启动了拖动。

draggableView.setOnLongClickListener{ v ->
   //drag logic here
   true
}

即使视图不可长时间点击,此回调也会使其可长时间点击。返回值为布尔值。True 表示拖动被回调消耗。

准备 ClipData:要拖动的数据

让我们定义要放置的数据。数据可以是任何类型,从简单的文本到视频。此数据封装在 ClipData 对象中。ClipData 对象包含一个或多个复杂的 ClipItem

ClipDescription 中定义了不同的 MIME 类型。

我们正在拖动源视图的图像网址。ClipData 主要有 3 个组件

  1. 标签:向用户显示正在拖动内容的简单文本
  2. MIME 类型:正在拖动项目的 MIME 类型。
  3. ClipItem:要拖动的项目,封装在 ClipData.Item 对象中

让我们创建 ClipData

val label = "Dragged Image Url"
val clipItem = ClipData.Item(v.tag as? CharSequence)
val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)
val draggedData = ClipData(
   label, mimeTypes, clipItem
)

开始拖放

现在我们已准备好要拖动的数据,让我们启动拖动。为此,我们将使用 startDragAndDrop

startDragAndDrop 方法接受 4 个参数

  1. data:以 ClipData 形式拖动的数据。
  2. shadowBuilder:DragShadowBuilder 用于构建视图的阴影。
  3. myLocalState:一个包含有关拖放操作的本地数据的对象。在将拖动事件分派到同一 Activity 中的视图时,可以通过 DragEvent.getLocalState() 获取此对象。
  4. Flags:用于控制拖放操作的标志。

调用此函数后,基于 View.DragShadowBuilder 类,将绘制拖动阴影。系统获得拖动阴影后,将通过向已实现 OnDragListener 接口的可见视图发送事件来启动拖放操作。

v.startDragAndDrop(
   draggedData,
   View.DragShadowBuilder(v),
   null,
   0
)

这样,我们就配置了视图以进行拖动并设置了要拖动的数据。最终实现如下所示。

fun setupDrag(draggableView: View) {
   draggableView.setOnLongClickListener { v ->
       val label = "Dragged Image Url"
       val clipItem = ClipData.Item(v.tag as? CharSequence)
       val mimeTypes = arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN)
       val draggedData = ClipData(
           label, mimeTypes, clipItem
       )
       v.startDragAndDrop(
           draggedData,
           View.DragShadowBuilder(v),
           null,
           0
       )
   }
}

在此阶段,您应该能够在长按时拖动视图。

526e9e2a7f3a90ea.gif

让我们继续配置放置的视图。

6. 配置视图作为放置目标

如果视图已实现 OnDragListener 接口,则该视图可以充当放置的目标。

让我们配置第二个 ImageView 以使其成为放置目标。

private fun setupDrop(dropTarget: View) {
   dropTarget.setOnDragListener { v, event ->
       // handle drag events here
       true
   }
}

我们正在覆盖 onDrag 方法,该方法属于 OnDragListener 接口。onDrag 方法有两个参数。

  1. 接收拖动事件的视图
  2. 拖动事件的事件对象

如果拖动事件成功处理,则此方法返回 true,否则返回 false。

DragEvent

它表示系统在拖放操作的不同阶段传输的数据包。此数据包封装了有关操作本身和所涉及数据的重要信息。

DragEvent 根据拖放操作的阶段具有不同的拖动操作

  1. ACTION_DRAG_STARTED:它表示拖放操作的开始。
  2. ACTION _DRAG_LOCATION:它表示用户已在已进入状态下释放拖动,即未在放置目标区域的边界内。
  3. ACTION_DRAG_ENTERED:它表示拖动的视图位于放置目标视图的边界内。
  4. ACTION_DROP:它表示用户已在放置目标区域内释放拖动。
  5. ACTION_DRAG_ENDED:它表示拖放操作已结束。
  6. ACTION_DRAG_EXITED:它表示拖放操作的结束。

验证 DragEvent

如果在 ACTION_DRAG_STARTED 事件中满足所有约束条件,您可以选择继续执行拖放操作。例如,在此示例中,我们可以检查传入的数据类型是否正确。

DragEvent.ACTION_DRAG_STARTED -> {
   Log.d(TAG, "ON DRAG STARTED")
   if (event.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
       (v as? ImageView)?.alpha = 0.5F
       v.invalidate()
       true
   } else {
       false
   }
}

在此示例中,我们检查了事件中的 ClipDescription 是否具有可接受的 MIME 类型。如果是,我们将提供视觉信号以表示相同内容并返回 true,表示正在处理拖动的数据。否则,我们将返回 false 以表示放置目标视图已放弃拖动。

处理放置数据

ACTION_DROP 事件中,我们可以选择对放置的数据执行什么操作。在此示例中,我们正在提取已添加到 ClipData 作为文本的网址。我们将此图像从网址放置到我们的放置目标 ImageView 中

DragEvent.ACTION_DROP -> {
   Log.d(TAG, "On DROP")
   val item: ClipData.Item = event.clipData.getItemAt(0)
   val dragData = item.text
   Glide.with(this).load(item.text).into(v as ImageView)
   (v as? ImageView)?.alpha = 1.0F
   true
}

除了处理放置之外,我们还可以配置当用户将视图拖动到放置目标视图的边界框内时会发生什么,以及当用户将视图拖出目标区域时会发生什么。

让我们在拖动的项目进入目标区域时添加一些视觉提示

DragEvent.ACTION_DRAG_ENTERED -> {
   Log.d(TAG, "ON DRAG ENTERED")
   (v as? ImageView)?.alpha = 0.3F
   v.invalidate()
   true
}

此外,当用户将视图拖出放置目标视图的边界框时,添加更多视觉提示。

DragEvent.ACTION_DRAG_EXITED -> {
   Log.d(TAG, "ON DRAG EXISTED")
   (v as? ImageView)?.alpha = 0.5F
   v.invalidate()
   true
}

添加更多视觉提示以表示拖放操作的结束。

DragEvent.ACTION_DRAG_ENDED -> {
   Log.d(TAG, "ON DRAG ENDED")
   (v as? ImageView)?.alpha = 1.0F
   true
}

在此阶段,您应该能够将图像拖动到目标图像视图,一旦放下,目标 ImageView 的图像将反映更改。

114238f666d84c6f.gif

7. 多窗口模式下的拖放

如果应用程序通过多窗口模式共享屏幕,则可以将项目从一个应用程序拖动到另一个应用程序。跨应用程序启用拖放的实现相同,只是我们需要在拖动期间添加标志并在放下期间添加权限。

在拖动期间配置标志

我们记得,startDragAndDrop 有一个参数用于指定标志,该标志依次控制拖放操作。

v.startDragAndDrop(
   draggedData,
   View.DragShadowBuilder(v),
   null,
   View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
)

View.DRAG_FLAG_GLOBAL 表示拖动可以跨越窗口边界,而 View.DRAG_FLAG_GLOBAL_URI_READ 表示拖动接收方能够读取内容 URI。

为了使放置目标能够从其他应用程序读取拖动的数据,放置目标视图必须声明读取权限。

val dropPermission = requestDragAndDropPermissions(event)

并且在处理完拖动数据后释放权限。

dropPermission.release()

拖动项目的最终处理如下所示

DragEvent.ACTION_DROP -> {
   Log.d(TAG, "On DROP")
   val dropPermission = requestDragAndDropPermissions(event)
   val item: ClipData.Item = event.clipData.getItemAt(0)
   val dragData = item.text
   Glide.with(this).load(item.text).into(v as ImageView)
   (v as? ImageView)?.alpha = 1.0F
   dropPermission.release()
   true
}

在此阶段,您应该能够将此图像拖动到另一个应用程序,并且可以正确处理从另一个应用程序拖动的数据。

8. 拖放库

Jetpack 提供了一个 DragAndDrop 库来简化拖放操作的实现。

让我们在 build.gradle.kts 中添加依赖项以使用 DragAndDrop 库。

implementation("androidx.draganddrop:draganddrop:1.0.0")

对于此练习,创建一个名为 DndHelperActivity.kt 的单独 Activity,该 Activity 具有垂直排列的 2 个 ImageView,其中一个充当拖动源,另一个充当放置目标。

修改 strings.xml 以添加字符串资源。

<string name="greeting_1">DragStartHelper and DropHelper</string>
<string name="target_url_1">https://services.google.com/fh/files/misc/qq9.jpeg</string>
<string name="source_url_1">https://services.google.com/fh/files/misc/qq8.jpeg</string>

更新 activity_dnd_helper.xml 以包含 ImageView。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:padding="24dp"
   tools:context=".DnDHelperActivity">

   <TextView
       android:id="@+id/tv_greeting"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toTopOf="@id/iv_source"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <ImageView
       android:id="@+id/iv_source"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drag_image"
       app:layout_constraintBottom_toTopOf="@id/iv_target"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/tv_greeting" />

   <ImageView
       android:id="@+id/iv_target"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drop_image"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>

最后在 DnDHelperActivity.kt 中初始化视图。

class DnDHelperActivity : AppCompatActivity() {
   private val binding by lazy(LazyThreadSafetyMode.NONE) {
       ActivityMainBinding.inflate(layoutInflater)
   }

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(binding.root)
       binding.tvGreeting.text = getString(R.string.greeting)
       Glide.with(this).asBitmap()
           .load(getString(R.string.source_url_1))
           .into(binding.ivSource)
       Glide.with(this).asBitmap()
           .load(getString(R.string.target_url_1))
           .into(binding.ivTarget)
       binding.ivSource.tag = getString(R.string.source_url_1)
   }
}

确保更新 AndroidManifest.xml 以将 DndHelperActivity 设为启动 Activity。

<activity
   android:name=".DnDHelperActivity"
   android:exported="true">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
</activity>

DragStartHelper

之前,我们通过实现 onLongClickListener 并调用 startDragAndDrop 来配置视图以使其可拖动。DragStartHelper 通过提供实用程序方法简化了实现。

DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
   // prepare clipData
   
   // startDrag and Drop
}.attach()

DragStartHelper 将要拖动的视图作为参数。在这里,我们实现了 OnDragStartListener 方法,在该方法中我们将准备剪贴板数据并启动拖放操作。

最终实现如下所示。

DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
   val item = ClipData.Item(view.tag as? CharSequence)
   val dragData = ClipData(
       view.tag as? CharSequence,
       arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
       item
   )
   view.startDragAndDrop(
       dragData,
       View.DragShadowBuilder(view),
       null,
       0
   )
}.attach()

DropHelper

DropHelper 通过提供名为 configureView 的实用程序方法简化了目标放置视图的配置。

configureView 接受 4 个参数

  1. Activity:当前活动
  2. dropTarget:正在配置的视图
  3. mimeTypes:正在放置的数据项目的 mimeTypes
  4. OnReceiveContentListener 接口用于处理放置的数据

自定义放置目标突出显示。

DropHelper.configureView(
   This, // Current Activity
   dropTarget,
   arrayOf("text/*"),
   DropHelper.Options.Builder().build()
) {
   // handle the dropped data
}

OnRecieveContentListener 接收放置的内容。它有两个参数

  1. View:放置内容的位置
  2. Payload:要放置的实际内容
private fun setupDrop(dropTarget: View) {
   DropHelper.configureView(
       this,
       dropTarget,
       arrayOf("text/*"),
   ) { _, payload: ContentInfoCompat ->
       // TODO: step through clips if one cannot be loaded
       val item = payload.clip.getItemAt(0)
       val dragData = item.text
       Glide.with(this)
           .load(dragData)
           .centerCrop().into(dropTarget as ImageView)
       // Consume payload by only returning remaining items
       val (_, remaining) = payload.partition { it == item }
       remaining
   }
}

在此阶段,您应该能够使用 DragStartHelper 和 DropHelper 拖放数据。

2e32d6cd80e19dcb.gif

配置放置区域突出显示

如您所见,当拖动项目进入放置区域时,放置区域会突出显示。使用 DropHelper.Options,我们可以自定义拖动项目进入视图边界时放置区域的突出显示方式。

DropHelper.Options 可用于配置放置目标区域的突出显示颜色和突出显示角半径。

DropHelper.Options.Builder()
   .setHighlightColor(getColor(R.color.green))
   .setHighlightCornerRadiusPx(16)
   .build()

这些选项应作为参数传递给 DropHelper 的 configureView 方法。

private fun setupDrop(dropTarget: View) {
   DropHelper.configureView(
       this,
       dropTarget,
       arrayOf("text/*"),
       DropHelper.Options.Builder()
           .setHighlightColor(getColor(R.color.green))
           .setHighlightCornerRadiusPx(16)
           .build(),
   ) { _, payload: ContentInfoCompat ->
       // TODO: step through clips if one cannot be loaded
       val item = payload.clip.getItemAt(0)
       val dragData = item.text
       Glide.with(this)
           .load(dragData)
           .centerCrop().into(dropTarget as ImageView)
       // Consume payload by only returning remaining items
       val (_, remaining) = payload.partition { it == item }
       remaining
   }
}

拖放时,您应该能够看到突出显示的颜色和半径。

9d5c1c78ecf8575f.gif

9. 接收富内容

OnReceiveContentListener 是接收富内容(包括文本、html、图像、视频等)的统一 API。内容可以从键盘、拖动或剪贴板插入视图。维护每个输入机制的回调可能很麻烦。OnReceiveContentListener 可用于接收文本、标记、音频、视频、图像和其他内容,使用单个 API。该 OnReceiveContentListener API 通过创建一个单个 API 来实现来整合这些不同的代码路径,因此您可以专注于您的应用程序特定逻辑,并让平台处理其余部分。

对于此练习,创建一个名为 ReceiveRichContentActivity.kt 的单独 Activity,该 Activity 具有垂直排列的 2 个 ImageView,其中一个充当拖动源,另一个充当放置目标。

修改 strings.xml 以添加字符串资源。

<string name="greeting_2">Rich Content Receiver</string>
<string name="target_url_2">https://services.google.com/fh/files/misc/qq1.jpeg</string>
<string name="source_url_2">https://services.google.com/fh/files/misc/qq3.jpeg</string>

更新 activity_receive_rich_content.xml 以包含 ImageView。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".ReceiveRichContentActivity">

   <TextView
       android:id="@+id/tv_greeting"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       app:layout_constraintBottom_toTopOf="@id/iv_source"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <ImageView
       android:id="@+id/iv_source"
       android:layout_width="320dp"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drag_image"
       app:layout_constraintBottom_toTopOf="@id/iv_target"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/tv_greeting" />

   <ImageView
       android:id="@+id/iv_target"
       android:layout_width="320dp"
       android:layout_height="wrap_content"
       android:contentDescription="@string/drop_image"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toBottomOf="@id/iv_source" />
</androidx.constraintlayout.widget.ConstraintLayout>

最后在 ReceiveRichContentActivity.kt 中初始化视图。

class ReceiveRichContentActivity : AppCompatActivity() {
   private val binding by lazy(LazyThreadSafetyMode.NONE) {
       ActivityReceiveRichContentBinding.inflate(layoutInflater)
   }
   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(binding.root)
       binding.tvGreeting.text = getString(R.string.greeting_2)
       Glide.with(this).asBitmap()
           .load(getString(R.string.source_url_2))
           .into(binding.ivSource)
       Glide.with(this).asBitmap()
           .load(getString(R.string.target_url_2))
           .into(binding.ivTarget)
       binding.ivSource.tag = getString(R.string.source_url_2)
   }
}

确保更新 AndroidManifest.xml 以将 DndHelperActivity 设为启动 Activity。

<activity
   android:name=".ReceiveRichContentActivity"
   android:exported="true">
   <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
   </intent-filter>
</activity>

让我们首先创建一个实现 OnReceiveContentListener. 的回调。

val listener = OnReceiveContentListener { view, payload ->
   val (textContent, remaining) =
       payload.partition { item: ClipData.Item -> item.text != null }
   if (textContent != null) {
       val clip = textContent.clip
       for (i in 0 until clip.itemCount) {
           val currentText = clip.getItemAt(i).text
           Glide.with(this)
               .load(currentText)
               .centerCrop().into(view as ImageView)
       }
   }
   remaining
}

在这里,我们实现了接口 OnRecieveContentListener。方法 onRecieveContent 有 2 个参数

  1. 正在接收数据的当前视图
  2. 来自键盘、拖动或剪贴板的数据有效负载,以 ContentInfoCompat 的形式

此方法返回未处理的有效负载。

在这里,我们使用 Partition 方法将有效负载分隔为文本内容和其他内容。我们根据需要处理文本数据,并返回其余有效负载

让我们处理我们想对拖动数据执行的操作。

val listener = OnReceiveContentListener { view, payload ->
   val (textContent, remaining) =
       payload.partition { item: ClipData.Item -> item.text != null }
   if (textContent != null) {
       val clip = textContent.clip
       for (i in 0 until clip.itemCount) {
           val currentText = clip.getItemAt(i).text
           Glide.with(this)
               .load(currentText)
               .centerCrop().into(view as ImageView)
       }
   }
   remaining
}

现在我们的侦听器已准备就绪。让我们将此侦听器添加到目标视图。

ViewCompat.setOnReceiveContentListener(
   binding.ivTarget,
   arrayOf("text/*"),
   listener
)

在此阶段,您应该能够拖动图像并将其放置到目标区域。放下后,拖动的图像应替换放置目标视图中的原始图像。

e4c3a3163c51135d.gif

10. 恭喜!

现在您已经熟练掌握了为您的 Android 应用程序实现拖放的方法。通过遵循此 Codelab,您学习了如何在 Android 应用程序内和不同应用程序之间创建交互式拖放交互,从而增强用户体验和功能。您已经学习了

  • 拖放基础知识:了解拖放事件的 4 个阶段(已开始、正在继续、已结束、已退出)以及 DragEvent 对象中的关键数据。
  • 启用拖放:通过处理 DragEvent 使视图可拖动并在目标视图中处理放置。
  • 多窗口模式下的拖放:通过设置适当的标志和权限来启用跨应用程序拖放。
  • 使用 DragAndDrop 库:使用 Jetpack 库简化拖放实现。
  • 接收富内容:实现使用统一 API 处理来自各种输入方法的不同内容类型(文本、图像、视频等)。

了解更多