1. 开始之前
本 Codelab 提供了关于为视图实现拖放功能基础知识的实践指导。您将学习如何在应用内以及跨不同应用启用视图的拖放功能。本 Codelab 将指导您使用 DropHelper 启用拖放、使用 ShadowBuilder 自定义拖动时的视觉反馈、添加跨应用拖动的权限以及实现一个通用的内容接收器。
前提条件
完成本 Codelab 需要
- 有构建 Android 应用的经验。
- 有使用 Activities、Fragments、View binding 和 xml-layouts 的经验。
您将学习如何
创建一个简单的应用,该应用将
- 使用 DragStartHelper和DropHelper实现拖放功能
- 更改 ShadowBuilder
- 添加跨应用拖动的权限
- 实现通用内容接收器。
所需工具
- Android Studio Jellyfish 或更高版本
- Android 设备或模拟器
2. 拖放事件
拖放过程可以看作是 4 个阶段的事件,这些阶段是
- 开始:系统响应用户的拖动手势启动拖放操作。
- 进行中:用户继续拖动,当进入目标视图时,拖动阴影构建器会生效。
- 已放置:用户在放置目标的边界框内释放拖动。
- 已结束:系统发送信号结束拖放操作。
系统在 DragEvent 对象中发送拖动事件。DragEvent 对象可以包含以下数据
- ActionType:基于拖放事件生命周期事件的事件操作值,例如- ACTION_DRAG_STARTED- ,- ACTION_DROP等。
- ClipData:正在拖动的数据,封装在- ClipData对象中。
- ClipDescription:关于- ClipData对象的元信息。
- Result:拖放操作的结果。
- X:拖动对象当前位置的 x 坐标。
- Y:拖动对象当前位置的 y 坐标。
3. 设置
创建一个新项目并选择“Empty Views Activity”模板

将所有参数保留为默认值。让项目同步和索引。您将看到 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 源文件,修改布局以包含两个 ImageViews,一个作为拖动源,另一个作为放置目标。
<?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 中添加图片 URL 和问候文本
<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)
   }
}
此时您的应用应显示问候文本和两张垂直方向的图片。

5. 使视图可拖动
要使特定视图可拖动,该视图必须在拖动手势时实现 startDragAndDrop() 方法。
当用户在视图上启动拖动时,我们为 onLongClickListener 实现一个回调。
draggableView.setOnLongClickListener{ v ->
   //drag logic here
   true
}
即使视图不可长按,此回调也会使其变为可长按。返回值为布尔值。True 表示回调已处理拖动。
准备 ClipData:要拖动的数据
定义我们要放置的数据。数据可以是任何类型,从简单文本到视频。这些数据封装在 ClipData 对象中。ClipData 对象包含一个或多个复杂的 ClipItem
并在 ClipDescription 中定义了不同的 MIME 类型。
我们正在拖动源视图的图片 URL。ClipData 主要包含 3 个组件
- 标签:用于向用户显示正在拖动内容的简单文本
- MIME 类型:正在拖动项目的 MIME 类型。
- 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 个参数
- data:以 ClipData.形式表示的正在拖动的数据。
- shadowBuilder:DragShadowBuilder,用于构建视图的阴影。
- myLocalState:一个包含关于拖放操作的本地数据的对象。当向同一 Activity 中的视图分发拖动事件时,可以通过 DragEvent.getLocalState() 获取此对象。
- 标志:用于控制拖放操作的标志。
调用此函数后,系统会根据 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
       )
   }
}
此时,您应该能够通过长按拖动视图。

接下来配置放置目标视图。
6. 配置视图作为放置目标
如果视图实现了 OnDragListener 接口,则可以作为放置目标。
让我们配置第二个图像视图,使其成为放置目标。
private fun setupDrop(dropTarget: View) {
   dropTarget.setOnDragListener { v, event ->
       // handle drag events here
       true
   }
}
我们正在覆盖 OnDragListener 接口的 onDrag 方法。onDrag 方法有两个参数。
- 接收到拖动事件的视图
- 拖动事件的对象
如果拖动事件成功处理,此方法返回 true,否则返回 false。
DragEvent
它表示系统在拖放操作不同阶段传输的数据包。此数据包封装了关于操作本身及相关数据的重要信息。
DragEvent 根据拖放操作的阶段具有不同的拖动操作
- ACTION_DRAG_STARTED:表示拖放操作的开始。
- ACTION _DRAG_LOCATION:当拖动点位于视图边界框内时发送到视图。
- ACTION_DRAG_ENTERED:表示拖动的视图进入了目标放置视图的边界。
- ACTION_DROP:表示用户在目标放置区域内释放了拖动。
- ACTION_DRAG_ENDED:表示拖放操作已结束。
- 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 中的 URL 作为文本提取出来。然后将此 URL 指向的图片设置到我们的目标图像视图中。
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 的图片将反映出变化。

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,其中包含两个垂直排列的 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 设置为 Launcher 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 方法,在该方法中我们将准备 ClipData 并启动拖放操作。
最终实现代码如下所示。
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 个参数
- Activity:当前 Activity
- dropTarget:正在配置的视图
- mimeTypes:正在放置的数据项的 MIME 类型
- OnReceiveContentListener接口,用于处理放置的数据
自定义放置目标的突出显示效果。
DropHelper.configureView(
   This, // Current Activity
   dropTarget,
   arrayOf("text/*"),
   DropHelper.Options.Builder().build()
) {
   // handle the dropped data
}
OnReceiveContentListener 接收放置的内容。它有两个参数
- View:内容被放置的视图
- 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 进行拖放操作。

配置放置区域的突出显示效果
如您所见,当拖动的项目进入放置区域时,放置区域会突出显示。使用 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
   }
}
进行拖放操作时,您应该能够看到突出显示颜色和圆角效果。

9. 接收富文本内容
OnReceiveContentListener 是用于接收富文本内容的统一 API,包括文本、HTML、图片、视频等。内容可以通过键盘、拖动或剪贴板插入到视图中。为每种输入机制维护回调可能会很麻烦。OnReceiveContentListener 可以使用单个 API 接收文本、标记、音频、视频、图片等内容。OnReceiveContentListener API 通过创建一个供实现的单一 API 来整合这些不同的代码路径,以便您可以专注于应用特有的逻辑,而让平台处理其余部分。
为了进行此练习,创建一个名为 ReceiveRichContentActivity.kt 的单独 Activity,其中包含两个垂直排列的 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 将 ReceiveRichContentActivity 设置为 Launcher 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
}
在这里我们实现了 OnReceiveContentListener 接口。方法 onReceiveContent 有两个参数
- 接收数据的当前视图
- 以 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
)
此时,您应该能够拖动图片并放置到目标区域。放置后,拖动的图片应替换放置目标视图中的原始图片。

10. 恭喜!
现在您已精通为 Android 应用实现拖放功能。通过完成本 Codelab,您学习了如何在 Android 应用内和跨不同应用创建交互式拖放交互,从而增强用户体验和功能。您学习了
- 拖放基础知识:了解拖放事件的 4 个阶段(开始、进行中、已放置、已结束)以及 DragEvent 对象中的关键数据。
- 启用拖放:通过处理 DragEvent 使视图可拖动并在目标视图中处理放置
- 多窗口模式下的拖放:通过设置适当的标志和权限启用跨应用拖放。
- 使用 DragAndDrop 库:使用 Jetpack 库简化拖放实现
- 接收富文本内容:使用统一 API 实现处理来自各种输入方法(文本、图片、视频等)的多种内容类型。
