Implementazione del trascinamento con visualizzazioni

Puoi implementare il processo di trascinamento nelle viste rispondendo agli eventi che potrebbero attivare un avvio tramite trascinamento e rispondendo agli eventi di rilascio.

Inizia a trascinare

L'utente avvia il trascinamento con un gesto, in genere toccando, facendo clic e tenendo premuto sull'elemento che vuole trascinare.

Per gestire questo problema in un View, crea un oggetto ClipData e un oggetto ClipData.Item per i dati da spostare. Nell'ambito di ClipData, fornisci i metadati archiviati in un oggetto ClipDescription all'interno di ClipData. Per un'operazione di trascinamento che non rappresenta lo spostamento dei dati, potresti utilizzare null anziché un oggetto reale.

Ad esempio, questo snippet di codice mostra come rispondere a un gesto tocco e pressione su un ImageView creando un oggetto ClipData contenente il tag (o l'etichetta) di un 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;
});

Rispondere a un inizio di trascinamento

Durante l'operazione di trascinamento, il sistema invia gli eventi di trascinamento agli ascoltatori degli eventi di trascinamento degli oggetti View nel layout corrente. Gli ascoltatori reagiscono chiamando DragEvent.getAction() per ottenere il tipo di azione. All'inizio di un trascinamento, questo metodo restituisce ACTION_DRAG_STARTED.

In risposta a un evento con il tipo di azione ACTION_DRAG_STARTED, un listener di eventi di trascinamento deve:

  1. Chiama DragEvent.getClipDescription() e utilizza i metodi di tipo MIME nell'oggetto ClipDescription restituito per verificare se il listener può accettare i dati trascinati.

    Se l'operazione di trascinamento non rappresenta lo spostamento dei dati, potrebbe non essere necessario.

  2. Se il listener di eventi di trascinamento può accettare un rilascio, deve restituire true per indicare al sistema di continuare a inviare eventi di trascinamento al listener. Se il listener non può accettare un lancio, deve restituire false e il sistema interrompe l'invio di eventi di trascinamento al listener finché il sistema non invia ACTION_DRAG_ENDED per completare l'operazione di trascinamento.

Per un evento ACTION_DRAG_STARTED, i seguenti metodi DragEvent non sono validi: getClipData(), getX(), getY() e getResult().

Gestire gli eventi durante il trascinamento

Durante l'azione di trascinamento, i listener di eventi di trascinamento che restituiscono true in risposta all'evento di trascinamento ACTION_DRAG_STARTED continuano a ricevere eventi di trascinamento. I tipi di eventi di trascinamento che un listener riceve durante il trascinamento dipendono dalla posizione dell'ombra di trascinamento e dalla visibilità del View del listener. I listener utilizzano gli eventi di trascinamento principalmente per decidere se modificare l'aspetto del proprio View.

Durante l'azione di trascinamento, DragEvent.getAction() restituisce uno dei tre valori seguenti:

  • ACTION_DRAG_ENTERED: il listener riceve questo tipo di azione evento quando il punto di contatto (il punto sullo schermo sotto il dito o il mouse dell'utente) inserisce il riquadro di delimitazione del View dell'ascoltatore.
  • ACTION_DRAG_LOCATION: quando il listener riceve un evento ACTION_DRAG_ENTERED, riceve un nuovo evento ACTION_DRAG_LOCATION ogni volta che il punto di contatto si sposta fino a quando non riceve un evento ACTION_DRAG_EXITED. I metodi getX() e getY() restituiscono le coordinate X e Y del punto di contatto.
  • ACTION_DRAG_EXITED: questo tipo di azione evento viene inviato a un listener che in precedenza riceve ACTION_DRAG_ENTERED. L'evento viene inviato quando il punto di contatto dell'ombra di trascinamento si sposta dall'interno del riquadro di delimitazione del valore View del listener all'esterno del riquadro di delimitazione.

Il listener di eventi di trascinamento non deve reagire a nessuno di questi tipi di azioni. Se il listener restituisce un valore al sistema, viene ignorato.

Di seguito sono riportate alcune linee guida per rispondere a ciascuno di questi tipi di azioni:

  • In risposta a ACTION_DRAG_ENTERED o ACTION_DRAG_LOCATION, il listener può modificare l'aspetto di View per indicare che la visualizzazione è un potenziale target di rilascio.
  • Un evento con il tipo di azione ACTION_DRAG_LOCATION contiene dati validi per getX() e getY() corrispondenti alla posizione del punto di contatto. L'ascoltatore può utilizzare queste informazioni per modificare l'aspetto di View nel punto di contatto o per determinare la posizione esatta in cui l'utente può rilasciare i contenuti.
  • In risposta a ACTION_DRAG_EXITED, il listener deve reimpostare eventuali modifiche dell'aspetto applicate in risposta a ACTION_DRAG_ENTERED o ACTION_DRAG_LOCATION. Questo indica all'utente che View non è più un target di lancio imminente.

Rispondere a un calo

Quando l'utente rilascia l'ombra di trascinamento su View e l'View indica in precedenza che può accettare i contenuti trascinati, il sistema invia un evento di trascinamento a View con il tipo di azione ACTION_DROP.

Il listener di eventi di trascinamento deve:

  1. Chiama getClipData() per ottenere l'oggetto ClipData originariamente fornito nella chiamata a startDragAndDrop() ed elaborare i dati. Se l'operazione di trascinamento non rappresenta lo spostamento dei dati, non è necessario.

  2. Restituisce il valore booleano true per indicare che il calo è stato elaborato correttamente, oppure false in caso contrario. Il valore restituito diventa il valore restituito da getResult() per l'eventuale evento ACTION_DRAG_ENDED. Se il sistema non invia un evento ACTION_DROP, il valore restituito da getResult() per un evento ACTION_DRAG_ENDED è false.

Per un evento ACTION_DROP, getX() e getY() usano il sistema di coordinate del View che riceve il calo per restituire la posizione X e Y del punto di contatto al momento del rilascio.

Anche se l'utente è in grado di rilasciare l'ombra di trascinamento su un View il cui listener di eventi di trascinamento non riceve eventi di trascinamento, regioni vuote dell'interfaccia utente della tua app o anche su aree esterne all'applicazione, Android non invierà un evento con il tipo di azione ACTION_DROP e invierà soltanto un evento ACTION_DRAG_ENDED.

Risposta a una fine del trascinamento

Subito dopo che l'utente rilascia l'ombra di trascinamento, il sistema invia un evento di trascinamento con un tipo di azione ACTION_DRAG_ENDED a tutti i listener di eventi di trascinamento nell'applicazione. Questo indica che l'operazione di trascinamento è terminata.

Ogni listener di eventi di trascinamento deve:

  1. Se il listener cambia l'aspetto durante l'operazione, dovrebbe ripristinare l'aspetto predefinito come un'indicazione visiva per l'utente che l'operazione è terminata.
  2. Il listener può facoltativamente chiamare getResult() per scoprire di più sull'operazione. Se un listener restituisce true in risposta a un evento di tipo azione ACTION_DROP, getResult() restituisce il valore booleano true. In tutti gli altri casi, getResult() restituisce false booleano, anche quando il sistema non invia un evento ACTION_DROP.
  3. Per indicare il completamento dell'operazione di rilascio, il listener deve restituire il valore booleano true al sistema. Se non restituisci false, un segnale visivo che mostra l'ombra che torna alla sua origine potrebbe suggerire all'utente che l'operazione non è riuscita.

Risposta agli eventi di trascinamento: esempio

Tutti gli eventi di trascinamento vengono ricevuti dal metodo degli eventi di trascinamento o dal listener. Lo snippet di codice riportato di seguito è un esempio di risposta agli eventi di trascinamento:

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;

});

Personalizza un'ombra trascinata

Puoi definire un myDragShadowBuilder personalizzato eseguendo l'override dei metodi in View.DragShadowBuilder. Lo snippet di codice riportato di seguito crea una piccola ombra rettangolare di colore grigio per un elemento 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);
    }
}