以下部分解释了拖放过程的一些关键概念。
拖放过程
拖放过程中有四个步骤或状态:开始、继续、放下和结束。
- 开始
响应用户的拖动手势,您的应用程序调用
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 所示
动作类型 | 含义 |
---|---|
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 所示。有关事件及其关联数据的更多信息,请参阅名为 拖放操作 的部分。
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()
来创建拖动阴影。该方法只有一个参数,一个Canvas
对象,系统根据您在onProvideShadowMetrics()
中提供的参数构建该对象。该方法在提供的Canvas
上绘制拖动阴影。
为了提高性能,请保持拖动阴影的大小较小。对于单个项目,您可能希望使用图标。对于多项选择,您可能希望使用堆叠中的图标,而不是在屏幕上展开的完整图像。
拖放事件监听器和回调方法
View
使用实现View.OnDragListener
的拖放事件监听器或视图的onDragEvent()
回调方法接收拖放事件。当系统调用该方法或监听器时,它会提供一个DragEvent
参数。
在大多数情况下,使用监听器优于使用回调方法。在设计UI时,通常不会对View
类进行子类化,但使用回调方法会强制您创建子类以覆盖该方法。相比之下,您可以实现一个监听器类,然后将其与多个不同的View
对象一起使用。您还可以将其实现为匿名内联类或lambda表达式。要为View
对象设置监听器,请调用setOnDragListener()
。
或者,您可以在不覆盖方法的情况下更改onDragEvent()
的默认实现。在视图上设置OnReceiveContentListener
;有关更多详细信息,请参阅setOnReceiveContentListener()
。然后,onDragEvent()
方法默认执行以下操作
- 对
startDragAndDrop()
的调用返回true。 如果拖放数据被放置在视图上,则调用
performReceiveContent()
。数据作为ContentInfo
对象传递给该方法。该方法调用OnReceiveContentListener
。如果拖放数据被放置在视图上并且
OnReceiveContentListener
使用了任何内容,则返回true。
定义OnReceiveContentListener
以专门为您的应用处理数据。为了向后兼容到 API 级别 24,请使用OnReceiveContentListener
的 Jetpack 版本。
View
对象可以同时拥有拖放事件监听器和回调方法,在这种情况下,系统会首先调用监听器。除非监听器返回false
,否则系统不会调用回调方法。
onDragEvent()
方法和View.OnDragListener
的组合类似于onTouchEvent()
和View.OnTouchListener
与触摸事件一起使用的组合。