Bạn có thể triển khai quy trình kéo và thả trong khung hiển thị bằng cách phản hồi các sự kiện có thể kích hoạt thao tác bắt đầu kéo, phản hồi và sử dụng các sự kiện thả.
Bắt đầu kéo
Người dùng bắt đầu kéo bằng một cử chỉ, thường bằng cách chạm hoặc nhấp và giữ một mục họ muốn kéo.
Để xử lý việc này trong View
, hãy tạo một
Đối tượng ClipData
và
ClipData.Item
đối tượng cho
dữ liệu đang được di chuyển. Trong ClipData
, hãy cung cấp siêu dữ liệu
được lưu trữ trong một
ClipDescription
đối tượng
trong ClipData
. Đối với thao tác kéo và thả không biểu thị
di chuyển dữ liệu, bạn có thể cần sử dụng null
thay vì một đối tượng thực tế.
Ví dụ: đoạn mã dưới đây cho biết cách phản hồi thao tác chạm và giữ trên ImageView
bằng cách tạo đối tượng ClipData
chứa thẻ (hoặc nhãn) của 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; });
Phản hồi khi bắt đầu kéo
Trong quá trình kéo, hệ thống sẽ gửi các sự kiện kéo đến trình nghe sự kiện kéo của các đối tượng View
trong bố cục hiện tại. Người nghe phản ứng bằng cách
gọi DragEvent.getAction()
để lấy loại thao tác. Khi bắt đầu kéo,
phương thức này trả về ACTION_DRAG_STARTED
.
Để phản hồi một sự kiện có loại thao tác ACTION_DRAG_STARTED
, một sự kiện kéo
trình nghe phải làm những việc sau:
Gọi điện
DragEvent.getClipDescription()
và sử dụng các phương thức loại MIME trongClipDescription
được trả về để xem liệu trình nghe có thể chấp nhận dữ liệu đang được kéo hay không.Nếu thao tác kéo và thả không thể hiện di chuyển dữ liệu, điều này có thể là không cần thiết.
Nếu trình nghe sự kiện kéo có thể chấp nhận thao tác thả, trình nghe phải trả về
true
để cho biết hệ thống để tiếp tục gửi các sự kiện kéo đến trình nghe. Nếu trình nghe không thể chấp nhận sự kiện thả, trình nghe phải trả vềfalse
và hệ thống sẽ ngừng gửi sự kiện kéo đến trình nghe cho đến khi hệ thống gửiACTION_DRAG_ENDED
để kết thúc thao tác kéo và thả.
Đối với sự kiện ACTION_DRAG_STARTED
, các phương thức DragEvent
sau không
hợp lệ: getClipData()
,
getX()
,
getY()
và
getResult()
.
Xử lý sự kiện trong quá trình kéo
Trong thao tác kéo, trình nghe sự kiện kéo trả về true
để phản hồi
sự kiện kéo ACTION_DRAG_STARTED
sẽ tiếp tục nhận các sự kiện kéo. Các loại
của các sự kiện kéo mà trình nghe nhận được trong quá trình kéo phụ thuộc vào vị trí của
bóng khi kéo và chế độ hiển thị View
của trình nghe. Trình nghe sử dụng thao tác kéo
các sự kiện chủ yếu để quyết định xem chúng có phải thay đổi giao diện của View
hay không.
Trong thao tác kéo, DragEvent.getAction()
sẽ trả về một trong ba giá trị:
ACTION_DRAG_ENTERED
: trình nghe sẽ nhận được loại hành động sự kiện này khi điểm tiếp xúc — trỏ trên màn hình bên dưới ngón tay hoặc chuột của người dùng—nhập hộp giới hạnView
của trình nghe.ACTION_DRAG_LOCATION
: sau khi nhận được một sự kiệnACTION_DRAG_ENTERED
, trình nghe sẽ nhận được một sự kiện mớiACTION_DRAG_LOCATION
sự kiện mỗi khi điểm tiếp xúc di chuyển cho đến khi điểm đó di chuyển sẽ nhận được một sự kiệnACTION_DRAG_EXITED
. Phương thứcgetX()
vàgetY()
trả về toạ độ X và Y của điểm tiếp xúc.ACTION_DRAG_EXITED
: loại hành động sự kiện này được gửi đến một trình nghe trước đó nhận đượcACTION_DRAG_ENTERED
. Sự kiện được gửi khi điểm chạm bóng khi kéo di chuyển từ bên trong hộp giới hạn củaView
của trình nghe ra bên ngoài hộp giới hạn.
Trình nghe sự kiện kéo không cần phải phản ứng với bất kỳ loại thao tác nào trong số này. Nếu trình nghe trả về một giá trị cho hệ thống thì giá trị này sẽ bị bỏ qua.
Dưới đây là một số nguyên tắc để phản hồi từng loại thao tác:
- Để phản hồi
ACTION_DRAG_ENTERED
hoặcACTION_DRAG_LOCATION
, trình nghe có thể thay đổi giao diện củaView
để cho biết thành phần hiển thị có thể là một mục tiêu thả. - Một sự kiện có loại hành động
ACTION_DRAG_LOCATION
chứa dữ liệu hợp lệ chogetX()
vàgetY()
tương ứng với vị trí của điểm tiếp xúc. Chiến lược phát hành đĩa đơn trình nghe có thể sử dụng thông tin này để thay đổi giao diệnView
ở điểm tiếp xúc hoặc để xác định vị trí chính xác mà người dùng có thể thả nội dung. - Để phản hồi
ACTION_DRAG_EXITED
, trình nghe phải đặt lại mọi giao diện các thay đổi sẽ được áp dụng đối vớiACTION_DRAG_ENTERED
hoặcACTION_DRAG_LOCATION
. Việc này cho người dùng biết rằngView
không còn là một mục tiêu thả sắp tới.
Phản hồi sự kiện thả
Khi người dùng thả bóng khi kéo trên View
và View
trước đó
báo cáo rằng hệ thống có thể chấp nhận nội dung đang được kéo, hệ thống sẽ gửi một
sự kiện kéo vào View
bằng loại thao tác ACTION_DROP
.
Trình nghe sự kiện kéo phải làm như sau:
Gọi
getClipData()
để lấy đối tượngClipData
ban đầu được cung cấp trong cuộc gọi đếnstartDragAndDrop()
và xử lý dữ liệu. Nếu thao tác kéo và thả không biểu thị dữ liệu chuyển động, điều này là không cần thiết.Trả về giá trị boolean
true
để cho biết sự kiện thả được xử lý thành công, hoặcfalse
nếu không phải. Giá trị trả về trở thành giá trị màgetResult()
trả về cho sự kiệnACTION_DRAG_ENDED
cuối cùng. Nếu hệ thống không gửi sự kiệnACTION_DROP
, giá trị đượcgetResult()
trả về cho sự kiệnACTION_DRAG_ENDED
làfalse
.
Đối với sự kiện ACTION_DROP
, getX()
và getY()
sử dụng hệ toạ độ của
View
nhận sự sụt giảm để trả về vị trí X và Y của
điểm tiếp xúc tại thời điểm thả.
Trong khi người dùng có thể thả bóng khi kéo qua View
có sự kiện kéo
trình nghe không nhận được các sự kiện kéo, các vùng trống trong giao diện người dùng của ứng dụng hoặc thậm chí
ở các khu vực bên ngoài ứng dụng của bạn, Android sẽ không gửi sự kiện kèm theo hành động
nhập ACTION_DROP
và sẽ chỉ gửi một sự kiện ACTION_DRAG_ENDED
.
Phản hồi khi kết thúc quá trình kéo
Ngay sau khi người dùng thả bóng khi kéo, hệ thống sẽ gửi một thao tác kéo
sự kiện có loại thao tác là ACTION_DRAG_ENDED
cho tất cả trình nghe sự kiện kéo
trong ứng dụng của bạn. Mã này cho biết thao tác kéo đã kết thúc.
Mỗi trình nghe sự kiện kéo phải làm như sau:
- Nếu thay đổi giao diện trong quá trình hoạt động, trình nghe sẽ đặt lại trở lại giao diện mặc định dưới dạng chỉ báo trực quan cho người dùng biết rằng đã kết thúc.
- Trình nghe có thể gọi
getResult()
nếu muốn để tìm hiểu thêm về thao tác. Nếu trình nghe trả vềtrue
để phản hồi một sự kiện hành động nhậpACTION_DROP
, sau đógetResult()
sẽ trả về giá trị booleantrue
. Trong tất cả các phương thức khác các trường hợp,getResult()
sẽ trả về booleanfalse
, kể cả khi hệ thống sẽ không gửi sự kiệnACTION_DROP
. - Để cho biết đã hoàn tất thành công thao tác thả, trình nghe
phải trả về giá trị boolean
true
cho hệ thống. Khi không trả lạifalse
, một chỉ dẫn trực quan cho thấy bóng đổ trở về nguồn có thể gợi ý cho người dùng cho biết thao tác không thành công.
Ví dụ về phản hồi sự kiện kéo
Tất cả sự kiện kéo sẽ được phương thức sự kiện kéo hoặc trình nghe nhận. Chiến lược phát hành đĩa đơn đoạn mã sau đây là một ví dụ về phản hồi các sự kiện kéo:
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; });
Tuỳ chỉnh bóng khi kéo
Bạn có thể xác định một myDragShadowBuilder
tuỳ chỉnh bằng cách ghi đè các phương thức trong
View.DragShadowBuilder
Đoạn mã sau đây sẽ tạo một
bóng khi kéo màu xám, hình chữ nhật cho 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); } }