核心概念

试试 Compose 方式
Jetpack Compose 是 Android 推荐的界面工具包。了解如何在 Compose 中使用拖放。

以下部分解释了拖放过程的几个核心概念。

拖放过程

拖放过程包括四个步骤或状态:开始、进行中、已放下和结束。

开始

为了响应用户的拖动手势,您的应用会调用 startDragAndDrop() 来告知系统开始拖放操作。此方法的参数提供以下信息:

  • 要拖动的数据。
  • 用于绘制拖动阴影的回调
  • 描述拖动数据的元数据
  • 系统通过回调您的应用来获取拖动阴影。然后,系统会在设备上显示拖动阴影。
  • 接下来,系统会向当前布局中所有 View 对象的拖动事件监听器发送一个操作类型为 ACTION_DRAG_STARTED 的拖动事件。要继续接收拖动事件(包括可能的放下事件),拖动事件监听器必须返回 true。这将向系统注册监听器。只有已注册的监听器才会继续接收拖动事件。此时,监听器还可以更改其拖放目标 View 对象的显示外观,以表明该视图可以接受放下事件。
  • 如果拖动事件监听器返回 false,则在系统发送操作类型为 ACTION_DRAG_ENDED 的拖动事件之前,它不会收到当前操作的拖动事件。通过返回 false,监听器会告知系统它对拖放操作不感兴趣,并且不希望接受拖动的数据。
进行中
用户继续拖动。当拖动阴影与拖放目标的边界框相交时,系统会向目标的拖动事件监听器发送一个或多个拖动事件。监听器可能会根据事件更改拖放目标 View 的显示外观。例如,如果事件表明拖动阴影进入了拖放目标的边界框(操作类型为 ACTION_DRAG_ENTERED),则监听器可以通过高亮显示 View 来做出反应。
已放下
用户在拖放目标的边界框内释放拖动阴影。系统会向拖放目标的监听器发送一个操作类型为 ACTION_DROP 的拖动事件。拖动事件对象包含在启动操作时调用 startDragAndDrop() 传递给系统的数据。如果监听器成功处理了放下的数据,则预期会向系统返回布尔值 true。:仅当用户在监听器已注册接收拖动事件的 View(即拖放目标)边界框内释放拖动阴影时,才会发生此步骤。如果用户在任何其他情况下释放拖动阴影,则不会发送 ACTION_DROP 拖动事件。
结束

用户释放拖动阴影后,如果需要,系统在发送

操作类型为 ACTION_DROP 的拖动事件后,系统会发送一个操作类型为 ACTION_DRAG_ENDED 的拖动事件,以指示拖放操作结束。无论用户在哪里释放拖动阴影,都会执行此操作。此事件会发送给所有已注册接收拖动事件的监听器,即使该监听器也接收了 ACTION_DROP 事件。

这些步骤的详细说明请参阅拖放操作部分。

拖动事件

系统会以 DragEvent 对象的形式发送拖动事件,其中包含描述拖放过程中正在发生情况的操作类型。根据操作类型,该对象还可能包含其他数据。

拖动事件监听器会接收 DragEvent 对象。要获取操作类型,监听器需要调用 DragEvent.getAction()。`DragEvent` 类中定义了六个可能的常量值,如表 1 所示

表 1. DragEvent 操作类型

操作类型 含义
ACTION_DRAG_STARTED 应用调用 startDragAndDrop() 并获取拖动阴影。如果监听器希望继续接收此操作的拖动事件,则必须向系统返回布尔值 true
ACTION_DRAG_ENTERED 拖动阴影进入拖动事件监听器 View 的边界框。这是拖动阴影进入边界框时监听器接收到的第一个事件操作类型。
ACTION_DRAG_LOCATION 紧随 ACTION_DRAG_ENTERED 事件之后,拖动阴影仍位于拖动事件监听器 View 的边界框内。
ACTION_DRAG_EXITED 在接收 ACTION_DRAG_ENTERED 和至少一个 ACTION_DRAG_LOCATION 事件后,拖动阴影移出了拖动事件监听器 View 的边界框。
ACTION_DROP 拖动阴影在拖动事件监听器 View 上释放。只有当监听器对 ACTION_DRAG_STARTED 拖动事件返回布尔值 true 时,才会向 View 对象的监听器发送此操作类型。如果用户在未注册监听器的 View 上释放拖动阴影,或者在不属于当前布局的任何位置释放拖动阴影,则不会发送此操作类型。

如果监听器成功处理了放下操作,则返回布尔值 true。否则,必须返回 false

ACTION_DRAG_ENDED 系统正在结束拖放操作。此操作类型不一定以 ACTION_DROP 事件为前导。如果系统发送 `ACTION_DROP`,接收 `ACTION_DRAG_ENDED` 操作类型并不意味着放下操作成功。监听器必须调用 getResult()(如表 2所示)来获取对 ACTION_DROP 的响应返回的值。如果未发送 ACTION_DROP 事件,则 getResult() 返回 false

`DragEvent` 对象还包含您的应用在调用 startDragAndDrop() 时提供给系统的数据和元数据。如表 2 所示,有些数据仅对某些操作类型有效。有关事件及其相关数据的详细信息,请参阅拖放操作部分。

表 2. 按操作类型区分的有效 DragEvent 数据

getAction()
getClipDescription()
getLocalState()
getX()
getY()
getClipData()
getResult()
ACTION_DRAG_STARTED        
ACTION_DRAG_ENTERED        
ACTION_DRAG_LOCATION    
ACTION_DRAG_EXITED        
ACTION_DROP  
ACTION_DRAG_ENDED        

`DragEvent` 方法 getAction()describeContents()writeToParcel()toString() 始终返回有效数据。

如果方法不包含特定操作类型的有效数据,则会根据其结果类型返回 null 或 0。

拖动阴影

在拖放操作期间,系统会显示用户拖动的图像。对于数据移动,此图像表示正在拖动的数据。对于其他操作,此图像表示拖动操作的某些方面。

此图像称为拖动阴影。您可以使用为 View.DragShadowBuilder 对象声明的方法创建它。使用 startDragAndDrop() 启动拖放操作时,您需要将构建器传递给系统。作为对 startDragAndDrop() 的响应的一部分,系统会调用您在 View.DragShadowBuilder 中定义的回调方法来获取拖动阴影。

`View.DragShadowBuilder` 类有两个构造函数:

View.DragShadowBuilder(View)

此构造函数接受您的应用的任何 View 对象。此构造函数会将 View 对象存储在 View.DragShadowBuilder 对象中,以便回调可以访问它来构建拖动阴影。此视图不必是用户选择以开始拖动操作的 View

如果您使用此构造函数,则无需扩展 View.DragShadowBuilder 或替换其方法。默认情况下,您会获得一个外观与您作为参数传入的 View 相同的拖动阴影,并居中显示在用户触摸屏幕的位置下方。

View.DragShadowBuilder()

如果您使用此构造函数,则 View.DragShadowBuilder 对象中不提供任何 View 对象。字段设置为 null。您必须扩展 View.DragShadowBuilder 并替换其方法,否则您会获得一个不可见的拖动阴影。系统不会抛出错误。

`View.DragShadowBuilder` 类包含两个共同创建拖动阴影的方法:

onProvideShadowMetrics()

在您调用 startDragAndDrop() 后,系统会立即调用此方法。您可以使用此方法将拖动阴影的尺寸和触摸点发送到系统。此方法有两个参数:

outShadowSize 一个 Point 对象。拖动阴影的宽度在 x 中,高度在 y 中。

outShadowTouchPoint 一个 Point 对象。触摸点是拖动阴影中在拖动过程中必须位于用户手指下方的点。其 X 位置位于 x 中,Y 位置位于 y 中。

onDrawShadow()

在调用 onProvideShadowMetrics() 后,系统会立即调用 onDrawShadow() 来创建拖动阴影。此方法有一个参数,即系统根据您在 onProvideShadowMetrics() 中提供的参数构建的 Canvas 对象。此方法会在提供的 Canvas 上绘制拖动阴影。

为了提高性能,请保持拖动阴影尺寸较小。对于单个项目,您可以使用图标。对于多项选择,您可以使用堆叠的图标,而不是分散在屏幕上的完整图片。

拖动事件监听器和回调方法

一个 View 通过实现 View.OnDragListener 的拖动事件监听器或使用视图的 onDragEvent() 回调方法来接收拖动事件。当系统调用该方法或监听器时,会提供一个 DragEvent 参数。

在大多数情况下,最好使用监听器而不是回调方法。在设计界面时,您通常不会创建 View 类的子类,但使用回调方法会迫使您创建子类来替换该方法。相比之下,您可以实现一个监听器类,然后将其用于多个不同的 View 对象。您还可以将其实现为匿名内联类或 Lambda 表达式。要为 View 对象设置监听器,请调用 setOnDragListener()

或者,您可以修改 onDragEvent() 的默认实现,而无需替换该方法。在视图上设置 OnReceiveContentListener;如需了解更多详情,请参阅 setOnReceiveContentListener()。然后,onDragEvent() 方法默认执行以下操作:

  • startDragAndDrop() 调用返回 true。
  • 如果拖放数据放在视图上,则调用 performReceiveContent()。数据将作为 ContentInfo 对象传递给该方法。此方法会调用 OnReceiveContentListener

  • 如果拖放数据放在视图上并且 OnReceiveContentListener 消耗了任何内容,则返回 true。

定义 OnReceiveContentListener 以专门处理您应用的数据。为了向后兼容到 API 级别 24,请使用 Jetpack 版本的 OnReceiveContentListener

您可以为一个 View 对象同时设置拖动事件监听器和回调方法,在这种情况下,系统会首先调用监听器。只有当监听器返回 false 时,系统才会调用回调方法。

`onDragEvent()` 方法和 View.OnDragListener 的组合类似于用于触摸事件的 onTouchEvent()View.OnTouchListener 的组合。