Вы можете реализовать процесс перетаскивания в представлениях, реагируя на события, которые могут вызвать начало перетаскивания, а также реагируя и обрабатывая события перетаскивания.
Начать перетаскивание
Пользователь начинает перетаскивание жестом, обычно касаясь или щелкая и удерживая элемент, который он хочет перетащить.
Чтобы справиться с этим в View
, создайте объект ClipData
и объект ClipData.Item
для перемещаемых данных. В рамках ClipData
укажите метаданные, которые хранятся в объекте ClipDescription
внутри ClipData
. Для операции перетаскивания, которая не представляет перемещение данных, вы можете использовать null
вместо реального объекта.
Например, в этом фрагменте кода показано, как реагировать на жест касания и удержания в ImageView
путем создания объекта ClipData
, содержащего тег (или метку) ImageView
:
Котлин
// 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 } }
Ява
// 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()
и используйте методы типа MIME в возвращенномClipDescription
чтобы узнать, может ли прослушиватель принять перетаскиваемые данные.Если операция перетаскивания не представляет перемещения данных, в этом может быть нет необходимости.
Если прослушиватель событий перетаскивания может принять удаление, он должен вернуть
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
ранее сообщает, что может принять перетаскиваемый контент, система отправляет событие перетаскивания в View
с типом действия ACTION_DROP
.
Прослушиватель событий перетаскивания должен сделать следующее:
Вызовите
getClipData()
, чтобы получить объектClipData
, который изначально предоставляется при вызовеstartDragAndDrop()
и обработайте данные. Если операция перетаскивания не представляет перемещение данных, в этом нет необходимости.Верните логическое значение
true
чтобы указать, что удаление обработано успешно, илиfalse
если это не так. Возвращаемое значение становится значением, возвращаемым методомgetResult()
для возможного событияACTION_DRAG_ENDED
. Если система не отправляет событиеACTION_DROP
, значение, возвращаемое методомgetResult()
для событияACTION_DRAG_ENDED
, равноfalse
.
Для события ACTION_DROP
getX()
и getY()
используют систему координат View
, которое получает падение, чтобы вернуть положение X и Y точки касания в момент падения.
Хотя пользователь может отпустить тень перетаскивания над View
, чей прослушиватель событий перетаскивания не получает события перетаскивания, пустые области пользовательского интерфейса вашего приложения или даже над областями за пределами вашего приложения, Android не отправит событие с типом действия ACTION_DROP
и отправит только событие ACTION_DRAG_ENDED
.
Реагировать на перетаскивание
Сразу после того, как пользователь отпускает тень перетаскивания, система отправляет событие перетаскивания с типом действия ACTION_DRAG_ENDED
всем прослушивателям событий перетаскивания в вашем приложении. Это означает, что операция перетаскивания завершена.
Каждый прослушиватель событий перетаскивания должен выполнить следующее:
- Если прослушиватель меняет свой внешний вид во время операции, он должен вернуться к внешнему виду по умолчанию в качестве визуального указания пользователю о завершении операции.
- Слушатель может дополнительно вызвать
getResult()
чтобы узнать больше об операции. Если прослушиватель возвращаетtrue
в ответ на событие типа действияACTION_DROP
, тоgetResult()
возвращает логическое значениеtrue
. Во всех остальных случаяхgetResult()
возвращает логическое значениеfalse
, включая случаи, когда система не отправляет событиеACTION_DROP
. - Чтобы указать на успешное завершение операции удаления, прослушиватель должен вернуть системе логическое значение
true
. Если не вернутьfalse
, визуальный сигнал, показывающий, как тень возвращается к своему источнику, может подсказать пользователю, что операция не удалась.
Реакция на события перетаскивания: пример
Все события перетаскивания принимаются вашим методом или прослушивателем событий перетаскивания. Следующий фрагмент кода является примером реагирования на события перетаскивания:
Котлин
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 } } }
Ява
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
:
Котлин
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) } }
Ява
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); } }