Sie können den Drag-and-drop-Prozess in Ansichten implementieren, indem Sie auf Ereignisse reagieren, die einen Drag-Start-Vorgang auslösen könnten, sowie auf Drop-Ereignisse reagieren und diese verarbeiten.
Ziehen starten
Der Nutzer beginnt das Ziehen mit einer Geste, in der Regel durch Berühren oder Klicken und Halten eines Elements, das er ziehen möchte.
Um dies in einem View
zu verarbeiten, erstellen Sie ein ClipData
-Objekt und ein ClipData.Item
-Objekt für die zu verschiebenden Daten. Geben Sie als Teil des ClipData
Metadaten an, die in einem ClipDescription
-Objekt im ClipData
gespeichert sind. Bei einem Drag-and-drop-Vorgang, der keine Datenbewegung darstellt, können Sie null
anstelle eines tatsächlichen Objekts verwenden.
Dieses Code-Snippet zeigt beispielsweise, wie Sie auf eine Touch-Geste für das Berühren und Halten eines ImageView
reagieren, indem Sie ein ClipData
-Objekt erstellen, das das Tag (oder Label) eines ImageView
enthält:
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; });
Auf Drag-and-Start reagieren
Während des Drag-Vorgangs sendet das System Drag-Ereignisse an die Drag-Event-Listener der View
-Objekte im aktuellen Layout. Die Listener reagieren, indem sie DragEvent.getAction()
aufrufen, um den Aktionstyp abzurufen. Zu Beginn eines Drag-Vorgangs gibt diese Methode ACTION_DRAG_STARTED
zurück.
Als Reaktion auf ein Ereignis mit dem Aktionstyp ACTION_DRAG_STARTED
muss ein Drag-Event-Listener Folgendes tun:
Rufen Sie
DragEvent.getClipDescription()
auf und verwenden Sie die MIME-Typ-Methoden in der zurückgegebenenClipDescription
, um festzustellen, ob der Listener die gezogenen Daten akzeptieren kann.Wenn der Drag-and-drop-Vorgang keine Datenbewegung darstellt, ist dies möglicherweise nicht erforderlich.
Wenn der Drag-Event-Listener ein Drop-down-Menü akzeptiert, muss er
true
zurückgeben. So wird das System angewiesen, weiterhin Drag-Ereignisse an den Listener zu senden. Wenn der Listener das Ablegen nicht akzeptieren kann, muss erfalse
zurückgeben. Das System stoppt das Senden von Drag-Ereignissen an den Listener, bis das SystemACTION_DRAG_ENDED
sendet, um den Drag-and-drop-Vorgang abzuschließen.
Für ein ACTION_DRAG_STARTED
-Ereignis sind die folgenden DragEvent
-Methoden nicht gültig: getClipData()
, getX()
, getY()
und getResult()
.
Ereignisse während des Ziehens verarbeiten
Während der Drag-Aktion empfangen Drag-Event-Listener, die true
als Reaktion auf das ACTION_DRAG_STARTED
-Drag-Ereignis zurückgeben, weiterhin Drag-Ereignisse. Welche Arten von Ziehereignissen ein Listener während des Ziehens empfängt, hängt von der Position des Drag-Schattens und der Sichtbarkeit des View
des Listeners ab. Listener verwenden die Drag-Ereignisse hauptsächlich, um zu entscheiden, ob sie das Aussehen ihrer View
ändern müssen.
Während der Ziehaktion gibt DragEvent.getAction()
einen von drei Werten zurück:
ACTION_DRAG_ENTERED
: Der Listener empfängt diesen Ereignisaktionstyp, wenn der Touchpoint (der Punkt auf dem Bildschirm unter dem Finger oder der Maus des Nutzers) in den Markierungsrahmen derView
des Listeners eintritt.ACTION_DRAG_LOCATION
: Sobald der Listener einACTION_DRAG_ENTERED
-Ereignis empfängt, empfängt er jedes Mal ein neuesACTION_DRAG_LOCATION
-Ereignis, wenn sich der Touchpoint bewegt, bis er einACTION_DRAG_EXITED
-Ereignis empfängt. Die MethodengetX()
undgetY()
geben die X- und Y-Koordinaten des Touchpoints zurück.ACTION_DRAG_EXITED
: Dieser Ereignisaktionstyp wird an einen Listener gesendet, der zuvorACTION_DRAG_ENTERED
empfängt. Das Ereignis wird gesendet, wenn sich der Ziehschattenberührungspunkt innerhalb des Begrenzungsrahmens vonView
des Listeners außerhalb des Markierungsrahmens bewegt.
Der Drag-Event-Listener muss auf keinen dieser Aktionstypen reagieren. Wenn der Listener dem System einen Wert zurückgibt, wird dieser ignoriert.
Im Folgenden finden Sie einige Richtlinien zum Umgang mit diesen Aktionstypen:
- Als Reaktion auf
ACTION_DRAG_ENTERED
oderACTION_DRAG_LOCATION
kann der Listener die Darstellung vonView
ändern, um anzuzeigen, dass die Ansicht ein potenzielles Drop-Ziel ist. - Ein Ereignis vom Aktionstyp
ACTION_DRAG_LOCATION
enthält gültige Daten fürgetX()
undgetY()
, die der Position des Touchpoints entsprechen. Der Listener kann diese Informationen verwenden, um dieView
-Darstellung am Touchpoint zu ändern oder die genaue Position zu bestimmen, an der der Nutzer den Inhalt ablegen kann. - Als Reaktion auf
ACTION_DRAG_EXITED
muss der Listener alle Darstellungsänderungen zurücksetzen, die er als Reaktion aufACTION_DRAG_ENTERED
oderACTION_DRAG_LOCATION
angewendet hat. Dies zeigt dem Nutzer an, dassView
kein direkter Drop-Ziel mehr ist.
Auf einen Rückgang reagieren
Wenn der Nutzer den Ziehschatten über einem View
loslässt und das View
zuvor meldet, dass der gezogene Inhalt akzeptiert werden kann, löst das System ein Drag-Ereignis an View
mit dem Aktionstyp ACTION_DROP
aus.
Der Drag-Event-Listener muss Folgendes tun:
Rufen Sie
getClipData()
auf, um dasClipData
-Objekt abzurufen, das ursprünglich im Aufruf vonstartDragAndDrop()
bereitgestellt wurde, und verarbeiten die Daten. Wenn der Drag-and-drop-Vorgang keine Datenbewegung darstellt, ist dies nicht erforderlich.Geben Sie den booleschen Wert
true
zurück, um anzugeben, dass der Rückgang erfolgreich verarbeitet wurde, oderfalse
, wenn dies nicht der Fall ist. Der zurückgegebene Wert wird zu dem Wert, der vongetResult()
für das letztendlicheACTION_DRAG_ENDED
-Ereignis zurückgegeben wird. Wenn das System keinACTION_DROP
-Ereignis sendet, wird vongetResult()
für einACTION_DRAG_ENDED
-Ereignis der Wertfalse
zurückgegeben.
Bei einem ACTION_DROP
-Ereignis verwenden getX()
und getY()
das Koordinatensystem des View
, der den Rückgang empfängt, um die X- und Y-Position des Touchpoints zum Zeitpunkt des Abfalls zurückzugeben.
Der Nutzer kann den Drag-Schatten über einem View
loslassen, dessen Drag-Event-Listener keine Drag-Ereignisse, leeren Bereichen der App-UI oder sogar Bereiche außerhalb der App empfängt. Android sendet jedoch kein Ereignis mit dem Aktionstyp ACTION_DROP
, sondern sendet nur ein ACTION_DRAG_ENDED
-Ereignis.
Auf Drag-and-drop reagieren
Sofort, nachdem der Nutzer den Ziehschatten loslässt, sendet das System ein Ziehereignis mit dem Aktionstyp ACTION_DRAG_ENDED
an alle Drag-Event-Listener in Ihrer App. Dies zeigt an, dass der Drag-Vorgang abgeschlossen ist.
Jeder Drag-Event-Listener muss Folgendes tun:
- Wenn der Listener während des Vorgangs die Darstellung ändert, sollte er auf die Standarddarstellung zurückgesetzt werden, um den Nutzer darüber zu informieren, dass der Vorgang abgeschlossen ist.
- Der Listener kann optional
getResult()
aufrufen, um mehr über den Vorgang zu erfahren. Wenn ein Listenertrue
als Antwort auf ein Ereignis des AktionstypsACTION_DROP
zurückgibt, gibtgetResult()
den booleschen Werttrue
zurück. In allen anderen Fällen gibtgetResult()
den booleschen Wertfalse
zurück, auch wenn das System keinACTION_DROP
-Ereignis sendet. - Der Listener sollte den booleschen Wert
true
an das System zurückgeben, um den erfolgreichen Abschluss des Löschvorgangs anzuzeigen. Wennfalse
nicht zurückgegeben wird, kann ein visueller Hinweis, dass der Schlagschatten zeigt, dass er zu seiner Quelle zurückkehrt, den Nutzer darauf hinweisen, dass der Vorgang fehlgeschlagen ist.
Auf Drag-Ereignisse reagieren: Ein Beispiel
Alle Drag-Ereignisse werden von der Methode oder dem Listener für Drag-Events empfangen. Das folgende Code-Snippet ist ein Beispiel für eine Reaktion auf Drag-Ereignisse:
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; });
Drag-Schatten anpassen
Sie können ein benutzerdefiniertes myDragShadowBuilder
definieren, indem Sie die Methoden in View.DragShadowBuilder
überschreiben. Mit dem folgenden Code-Snippet wird ein kleiner, rechteckiger, grauer Ziehschatten für ein TextView
erstellt:
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); } }