以下部分介绍了拖放过程的一些关键概念。
拖放过程
拖放过程包含四个步骤或状态:已开始、持续中、已丢弃和已结束。
- 已开始
为了响应用户的拖动手势,您的应用要调用
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 中所述。如需详细了解事件及其关联的数据,请参阅名为A
拖放操作的部分。
表 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,具体取决于其结果类型。
拖动阴影
在拖放操作期间,系统会显示用户拖动的图片。对于数据移动操作,此图片表示用户正在拖动的数据。对于其他操作,此图片表示拖动操作的某个方面。
此图片称为拖动阴影 。您可以使用为
a
View.DragShadowBuilder
对象声明的方法来创建它。当您使用 startDragAndDrop() 启动拖放操作时,会将构建器传递给系统。作为对 startDragAndDrop() 响应的一部分,系统会通过调用您在 View.DragShadowBuilder 中定义的回调方法来获取拖动阴影。
View.DragShadowBuilder 类有两个构造函数:
View.DragShadowBuilder(View)该构造函数可接受您的应用的任何
View对象。该构造函数会将View对象存储在View.DragShadowBuilder对象中,以便回调 可以获取视图对象来构造拖动阴影。该视图不一定是用户选择用于启动拖动操作的View。如果您使用该构造函数,则无需扩展
View.DragShadowBuilder,也无需替换其方法。默认情况下,您获取的拖动阴影与您作为参数传递的View具有相同外观,并且中心点位于用户轻触屏幕的位置。View.DragShadowBuilder()如果您使用该构造函数,
View对象中将没有任何可用的View.DragShadowBuilder对象。该字段会被设为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
的组合类似。