以下部分介绍了拖放过程的一些关键概念。
拖放过程
拖放过程包含四个步骤或状态:已开始、正在继续、已放下和已结束。
- 已开始
为了响应用户的拖动手势,您的应用要调用
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 上释放。仅当 View 对象的监听器返回布尔值 true 作为对 ACTION_DRAG_STARTED 拖动事件的响应时,系统才会将此操作类型发送至监听器。如果用户将拖动阴影释放到监听器未注册的 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()
方法会默认执行以下操作:
- 返回 true 以响应对
startDragAndDrop()
的调用。 如果用户将拖放数据放到该视图上,则调用
performReceiveContent()
。数据会作为ContentInfo
对象传递给该方法。该方法会调用OnReceiveContentListener
。如果用户将拖放数据放到该视图上,并且
OnReceiveContentListener
使用了其中的任何内容,则返回 true。
定义 OnReceiveContentListener
以专门处理您的应用的数据。为了向后兼容到 API 级别 24,请使用 OnReceiveContentListener
的 Jetpack 版本。
您可以为 View
对象使用拖动事件监听器和回调方法,在这种情况下,系统会先调用监听器。除非监听器返回 false
,否则系统不会调用回调方法。
onDragEvent()
方法和 View.OnDragListener
的组合与用于触摸事件的 onTouchEvent()
和 View.OnTouchListener
的组合类似。