Вы можете реализовать процесс перетаскивания в представлениях, реагируя на события, которые могут инициировать начало перетаскивания, а также реагируя на события перетаскивания и используя их.
Начать перетаскивание
Пользователь начинает перетаскивание с помощью жеста, обычно касаясь или нажимая и удерживая элемент, который он хочет перетащить.
Чтобы обработать это в 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()и используйте методы 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не возвращается, визуальная подсказка, показывающая, что тень возвращается к своему источнику, может указывать пользователю на то, что операция не удалась.
Пример реагирования на события перетаскивания
Все события перетаскивания обрабатываются методом или прослушивателем событий перетаскивания. В следующем фрагменте кода показано, как реагировать на события перетаскивания:
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); } }