您可以通过响应事件,在视图中实现拖放过程 可能会触发拖动开始、响应和使用放下事件的事件。
开始拖动
用户使用手势开始拖拽,通常通过轻触或点击和 按住他们想要拖动的项
如需在 View
中处理这种情况,请创建一个
ClipData
对象和
ClipData.Item
对象:
要移动的数据。在 ClipData
中,请提供符合以下条件的元数据:
存储在
ClipDescription
对象
在 ClipData
内。如果拖放操作不代表
数据移动,则可能需要使用 null
而不是实际对象。
例如,以下代码段展示了,如何通过创建包含 ImageView
的标记(或标签)的 ClipData
对象,来响应 ImageView
上的轻触并按住手势:
Kotlin
// Create a string for the ImageView label. val IMAGEVIEW_TAG = "icon bitmap" ... val imageView = ImageView(context).apply { // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. setImageBitmap(iconBitmap) tag = IMAGEVIEW_TAG setOnLongClickListener { v -> // Create a new ClipData. This is done in two steps to provide // clarity. The convenience method ClipData.newPlainText() can // create a plain text ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. val item = ClipData.Item(v.tag as? CharSequence) // Create a new ClipData using the tag as a label, the plain text // MIME type, and the already-created item. This creates a new // ClipDescription object within the ClipData and sets its MIME type // to "text/plain". val dragData = ClipData( v.tag as? CharSequence, arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN), item) // Instantiate the drag shadow builder. We use this imageView object // to create the default builder. val myShadow = View.DragShadowBuilder(view: this) // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged. myShadow, // The drag shadow builder. null, // No need to use local data. 0 // Flags. Not currently used, set to 0. ) // Indicate that the long-click is handled. true } }
Java
// Create a string for the ImageView label. private static final String IMAGEVIEW_TAG = "icon bitmap"; ... // Create a new ImageView. ImageView imageView = new ImageView(context); // Set the bitmap for the ImageView from an icon bitmap defined elsewhere. imageView.setImageBitmap(iconBitmap); // Set the tag. imageView.setTag(IMAGEVIEW_TAG); // Set a long-click listener for the ImageView using an anonymous listener // object that implements the OnLongClickListener interface. imageView.setOnLongClickListener( v -> { // Create a new ClipData. This is done in two steps to provide clarity. The // convenience method ClipData.newPlainText() can create a plain text // ClipData in one step. // Create a new ClipData.Item from the ImageView object's tag. ClipData.Item item = new ClipData.Item((CharSequence) v.getTag()); // Create a new ClipData using the tag as a label, the plain text MIME type, // and the already-created item. This creates a new ClipDescription object // within the ClipData and sets its MIME type to "text/plain". ClipData dragData = new ClipData( (CharSequence) v.getTag(), new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item); // Instantiate the drag shadow builder. We use this imageView object // to create the default builder. View.DragShadowBuilder myShadow = new View.DragShadowBuilder(imageView); // Start the drag. v.startDragAndDrop(dragData, // The data to be dragged. myShadow, // The drag shadow builder. null, // No need to use local data. 0 // Flags. Not currently used, set to 0. ); // Indicate that the long-click is handled. return true; });
响应拖动开始事件
在拖动操作期间,系统会向当前布局中 View
对象的拖动事件监听器发送拖动事件。监听器的回应方式
调用 DragEvent.getAction()
来获取操作类型。拖动开始时,
此方法会返回 ACTION_DRAG_STARTED
。
为响应操作类型为 ACTION_DRAG_STARTED
的事件,系统会记录一个拖动事件
监听器必须执行以下操作:
致电
DragEvent.getClipDescription()
并使用返回的ClipDescription
中的 MIME 类型方法查看 表示监听器是否可以接受正在拖动的数据。如果拖放操作不表示数据移动, 没有必要。
如果拖动事件监听器可以接受放下操作,则必须返回
true
以告知 系统继续向监听器发送拖动事件。如果监听器 不能接受放下操作,监听器必须返回false
,并且系统停止 向监听器发送拖动事件,直到系统发送ACTION_DRAG_ENDED
即可结束拖放操作。
对于 ACTION_DRAG_STARTED
事件,以下 DragEvent
方法不适用
有效值:getClipData()
,
getX()
,
getY()
和
getResult()
。
在拖动期间处理事件
在拖动操作期间,拖动会返回 true
的事件监听器来响应
ACTION_DRAG_STARTED
拖动事件会继续接收拖动事件。类型
监听器在拖动期间接收的拖动事件的数量取决于
拖动阴影以及监听器 View
的可见性。监听器使用
事件主要用于决定是否必须更改其 View
的外观。
在拖动操作期间,DragEvent.getAction()
会返回以下三个值之一:
ACTION_DRAG_ENTERED
: 当接触点(即 某个点,即可进入 监听器的View
的边界框。ACTION_DRAG_LOCATION
: 监听器收到ACTION_DRAG_ENTERED
事件后,会在其收到一个新的ACTION_DRAG_LOCATION
事件 收到ACTION_DRAG_EXITED
事件。getX()
和getY()
方法 返回接触点的 X 和 Y 坐标。ACTION_DRAG_EXITED
: 此事件操作类型发送到之前接收ACTION_DRAG_ENTERED
。当拖动阴影的接触点从监听器的View
的边界框内移到边界框外时,系统会发送该事件。
拖动事件监听器不需要对上述任何操作类型做出反应。如果 监听器会向系统返回值,该值将被忽略。
以下是响应以上各种操作类型的一些准则:
- 在响应
ACTION_DRAG_ENTERED
或ACTION_DRAG_LOCATION
时,监听器可更改View
的外观,以表明该视图是可能的拖放目标。 - 操作类型为“
ACTION_DRAG_LOCATION
”的事件包含getX()
和getY()
,分别对应接触点的位置。通过 监听器可以使用此信息来更改View
属性在 来确定用户可将内容放置到哪个位置 内容。 - 在响应
ACTION_DRAG_EXITED
时,监听器必须重置所有外观 在响应ACTION_DRAG_ENTERED
或ACTION_DRAG_LOCATION
。以便向用户表明,View
已不再是当下的拖放目标。
响应放下事件
当用户将拖动阴影释放到 View
上时,而先前的 View
可接受正在拖动的内容,则系统会发送一个
将操作类型为 ACTION_DROP
的事件拖动到 View
上。
拖动事件监听器必须执行以下操作:
调用
getClipData()
以获取最初的ClipData
对象 在对startDragAndDrop()
和处理数据如果拖放操作不表示数据 没有必要这样做返回布尔值
true
,表示已成功处理放下事件; 否则为false
。返回的值将成为getResult()
,适用于最终的ACTION_DRAG_ENDED
事件。如果系统 未发出ACTION_DROP
事件,则getResult()
返回的值ACTION_DRAG_ENDED
事件的触发条件为false
。
对于 ACTION_DROP
事件,getX()
和 getY()
会使用
接收放下操作的 View
,以返回
所有接触点都有可能出现
而用户能够将拖动阴影释放到其拖动事件的 View
上。
监听器未接收拖动事件、应用界面的空白区域,甚至
应用区域以外的区域,Android 不会发送包含操作的事件
类型ACTION_DROP
,并且只会发送ACTION_DRAG_ENDED
事件。
响应拖动结束事件
在用户释放拖动阴影后,系统会立即发送拖动
操作类型为 ACTION_DRAG_ENDED
的事件附加到所有拖动事件监听器
。这表示拖动操作已完成。
每个拖动事件监听器都必须执行以下操作:
- 如果监听器在操作期间改变了外观,应重置监听器 恢复其默认外观,以视觉指示 操作完成时的状态。
- 监听器可以选择调用
getResult()
,以了解关于该操作的更多信息。如果监听器在响应操作事件时返回true
类型ACTION_DROP
,则getResult()
会返回布尔值true
。在所有其他 在这种情况下,getResult()
会返回布尔值false
,包括当系统 不会发送ACTION_DROP
事件。 - 为了指示放置操作成功完成,监听器
应向系统返回布尔值
true
。如果不返回false
, 显示阴影返回到来源的视觉提示 操作失败的用户。
响应拖动事件:示例
所有拖动事件均由拖动事件方法或监听器接收。通过 以下代码段是一个响应拖动事件的示例:
Kotlin
val imageView = ImageView(this) // Set the drag event listener for the View. imageView.setOnDragListener { v, e -> // Handle each of the expected events. when (e.action) { DragEvent.ACTION_DRAG_STARTED -> { // Determine whether this View can accept the dragged data. if (e.clipDescription.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply a blue color tint to the View to // indicate that it can accept data. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true to indicate that the View can accept the dragged // data. true } else { // Return false to indicate that, during the current drag and // drop operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. false } } DragEvent.ACTION_DRAG_ENTERED -> { // Apply a green tint to the View. (v as? ImageView)?.setColorFilter(Color.GREEN) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DRAG_LOCATION -> // Ignore the event. true DragEvent.ACTION_DRAG_EXITED -> { // Reset the color tint to blue. (v as? ImageView)?.setColorFilter(Color.BLUE) // Invalidate the view to force a redraw in the new tint. v.invalidate() // Return true. The value is ignored. true } DragEvent.ACTION_DROP -> { // Get the item containing the dragged data. val item: ClipData.Item = e.clipData.getItemAt(0) // Get the text data from the item. val dragData = item.text // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is $dragData", Toast.LENGTH_LONG).show() // Turn off color tints. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Return true. DragEvent.getResult() returns true. true } DragEvent.ACTION_DRAG_ENDED -> { // Turn off color tinting. (v as? ImageView)?.clearColorFilter() // Invalidate the view to force a redraw. v.invalidate() // Do a getResult() and display what happens. when(e.result) { true -> Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG) else -> Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG) }.show() // Return true. The value is ignored. true } else -> { // An unknown action type is received. Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.") false } } }
Java
View imageView = new ImageView(this); // Set the drag event listener for the View. imageView.setOnDragListener( (v, e) -> { // Handle each of the expected events. switch(e.getAction()) { case DragEvent.ACTION_DRAG_STARTED: // Determine whether this View can accept the dragged data. if (e.getClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { // As an example, apply a blue color tint to the View to // indicate that it can accept data. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true to indicate that the View can accept the dragged // data. return true; } // Return false to indicate that, during the current drag-and-drop // operation, this View doesn't receive events again until // ACTION_DRAG_ENDED is sent. return false; case DragEvent.ACTION_DRAG_ENTERED: // Apply a green tint to the View. ((ImageView)v).setColorFilter(Color.GREEN); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DRAG_LOCATION: // Ignore the event. return true; case DragEvent.ACTION_DRAG_EXITED: // Reset the color tint to blue. ((ImageView)v).setColorFilter(Color.BLUE); // Invalidate the view to force a redraw in the new tint. v.invalidate(); // Return true. The value is ignored. return true; case DragEvent.ACTION_DROP: // Get the item containing the dragged data. ClipData.Item item = e.getClipData().getItemAt(0); // Get the text data from the item. CharSequence dragData = item.getText(); // Display a message containing the dragged data. Toast.makeText(this, "Dragged data is " + dragData, Toast.LENGTH_LONG).show(); // Turn off color tints. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Return true. DragEvent.getResult() returns true. return true; case DragEvent.ACTION_DRAG_ENDED: // Turn off color tinting. ((ImageView)v).clearColorFilter(); // Invalidate the view to force a redraw. v.invalidate(); // Do a getResult() and displays what happens. if (e.getResult()) { Toast.makeText(this, "The drop was handled.", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "The drop didn't work.", Toast.LENGTH_LONG).show(); } // Return true. The value is ignored. return true; // An unknown action type is received. default: Log.e("DragDrop Example","Unknown action type received by View.OnDragListener."); break; } return false; });
自定义拖动阴影
您可以通过重写 myDragShadowBuilder
View.DragShadowBuilder
。以下代码段会创建一个
TextView
的灰色矩形拖动阴影:
Kotlin
private class MyDragShadowBuilder(view: View) : View.DragShadowBuilder(view) { private val shadow = ColorDrawable(Color.LTGRAY) // Define a callback that sends the drag shadow dimensions and touch point // back to the system. override fun onProvideShadowMetrics(size: Point, touch: Point) { // Set the width of the shadow to half the width of the original // View. val width: Int = view.width / 2 // Set the height of the shadow to half the height of the original // View. val height: Int = view.height / 2 // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height) // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height) // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2) } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). override fun onDrawShadow(canvas: Canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas) } }
Java
private static class MyDragShadowBuilder extends View.DragShadowBuilder { // The drag shadow image, defined as a drawable object. private static Drawable shadow; // Constructor. public MyDragShadowBuilder(View view) { // Store the View parameter. super(view); // Create a draggable image that fills the Canvas provided by the // system. shadow = new ColorDrawable(Color.LTGRAY); } // Define a callback that sends the drag shadow dimensions and touch point // back to the system. @Override public void onProvideShadowMetrics (Point size, Point touch) { // Define local variables. int width, height; // Set the width of the shadow to half the width of the original // View. width = getView().getWidth() / 2; // Set the height of the shadow to half the height of the original // View. height = getView().getHeight() / 2; // The drag shadow is a ColorDrawable. Set its dimensions to // be the same as the Canvas that the system provides. As a result, // the drag shadow fills the Canvas. shadow.setBounds(0, 0, width, height); // Set the size parameter's width and height values. These get back // to the system through the size parameter. size.set(width, height); // Set the touch point's position to be in the middle of the drag // shadow. touch.set(width / 2, height / 2); } // Define a callback that draws the drag shadow in a Canvas that the system // constructs from the dimensions passed to onProvideShadowMetrics(). @Override public void onDrawShadow(Canvas canvas) { // Draw the ColorDrawable on the Canvas passed in from the system. shadow.draw(canvas); } }