以下部分解释了拖放过程的几个核心概念。
拖放过程
拖放过程包括四个步骤或状态:开始、进行中、已放下和结束。
- 开始
为了响应用户的拖动手势,您的应用会调用
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 上释放拖动阴影,或者在不属于当前布局的任何位置释放拖动阴影,则不会发送此操作类型。如果监听器成功处理了放下操作,则返回布尔值 |
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
的组合。