主要概念

下面几部分介绍了拖放流程的一些关键概念。

拖放过程

拖放过程有四个步骤或状态:已开始、继续、放下和结束。

已启动

为响应用户的拖动手势,您的应用会调用 startDragAndDrop(),以告知系统开始执行拖放操作。该方法的参数提供以下内容:

  • 要拖动的数据。
  • 用于绘制拖动阴影的回调
  • 用于描述所拖动数据的元数据
  • 系统会通过回调应用进行响应,以获取拖动阴影。然后,系统会在设备上显示拖动阴影。
  • 接下来,系统会将操作类型为 ACTION_DRAG_STARTED 的拖动事件发送到当前布局中所有 View 对象的拖动事件监听器。如需继续接收拖动事件(包括可能的放下事件),拖动事件监听器必须返回 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 之上。仅当 View 对象的监听器返回布尔值 true 来响应 ACTION_DRAG_STARTED 拖动事件时,系统才会将此操作类型发送至该监听器。如果用户将拖动阴影释放到监听器未注册的 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() 方法默认会执行以下操作:

  • 返回 true 以响应对 startDragAndDrop() 的调用。
  • 如果将拖放数据放到该视图中,则调用 performReceiveContent()。数据会作为 ContentInfo 对象传递给该方法。该方法会调用 OnReceiveContentListener

  • 如果拖放数据被放到视图上,并且 OnReceiveContentListener 使用了其中的任何内容,则返回 true。

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

您可以为 View 对象添加拖动事件监听器和回调方法,在这种情况下,系统会先调用监听器。除非监听器返回 false,否则系统不会调用回调方法。

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