关键概念

以下部分解释了拖放过程的一些关键概念。

拖放过程

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

开始

响应用户的拖动手势,您的应用程序调用 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 上释放拖动阴影,或者如果用户在当前布局之外的任何内容上释放拖动阴影,则不会发送此操作类型。

如果侦听器成功处理了放置,则返回布尔值 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()来创建拖动阴影。该方法只有一个参数,一个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与触摸事件一起使用的组合。