Cómo implementar la función de arrastrar y soltar con vistas

Puedes implementar el proceso de arrastrar y soltar en vistas si respondes a eventos. que podrían activar un inicio de arrastre, y una respuesta y un consumo de eventos de soltar.

Inicia un arrastre

El usuario inicia un arrastre con un gesto, generalmente al tocar o hacer clic y sosteniendo un elemento que quieren arrastrar.

Para controlar esto en un View, crea un objeto ClipData y objeto ClipData.Item para los datos que se están moviendo. Como parte de ClipData, proporciona metadatos que sean almacenado en una Objeto ClipDescription dentro de ClipData. Para una operación de arrastrar y soltar que no representa movimiento de datos, es posible que quieras usar null en lugar de un objeto real.

Por ejemplo, en este fragmento de código, se muestra cómo responder a un gesto de mantener presionado en un ImageView creando un objeto ClipData que contiene la etiqueta de 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;
});

Responde a un inicio de arrastre

Durante la operación de arrastre, el sistema despacha eventos de arrastre a los objetos de escucha de eventos de arrastre de los objetos View del diseño actual. Los oyentes reaccionan Llamando a DragEvent.getAction() para obtener el tipo de acción Al comienzo de un arrastre, este método muestra ACTION_DRAG_STARTED.

En respuesta a un evento con el tipo de acción ACTION_DRAG_STARTED, se crea un evento de arrastre objeto de escucha debe hacer lo siguiente:

  1. Llamada DragEvent.getClipDescription() y usa los métodos de tipo de MIME en la ClipDescription que se devuelve para ver si el objeto de escucha puede aceptar los datos que se arrastran.

    Si la operación de arrastrar y soltar no representa movimiento de datos, es posible que ser innecesarias.

  2. Si el objeto de escucha de eventos de arrastre puede aceptar la acción de soltar, debe mostrar true para indicarlo. el sistema para seguir enviando eventos de arrastre al objeto de escucha. Si el objeto de escucha no puede aceptar la acción de soltar, el objeto de escucha debe mostrar false y el sistema se detiene enviar eventos de arrastre al receptor hasta que el sistema envíe ACTION_DRAG_ENDED para concluir la operación de arrastrar y soltar.

Para un evento ACTION_DRAG_STARTED, no se incluyen los siguientes métodos DragEvent válida: getClipData(), getX(), getY() y getResult().

Controla eventos durante el arrastre

Durante la acción de arrastre, los objetos de escucha de eventos de arrastre que muestran true en respuesta a El evento de arrastre ACTION_DRAG_STARTED seguirá recibiendo eventos de arrastre. Los tipos de eventos de arrastre que recibe un receptor durante el arrastre dependen de la ubicación del y la visibilidad del elemento View del objeto de escucha. Los objetos de escucha usan la función de arrastrar eventos principalmente para decidir si deben cambiar el aspecto de su View.

Durante el arrastre, DragEvent.getAction() muestra uno de tres valores:

  • ACTION_DRAG_ENTERED: el objeto de escucha recibe este tipo de acción de evento cuando el punto táctil (el punto en la pantalla debajo del dedo o mouse del usuario, ingresa el cuadro de límite del elemento View del objeto de escucha.
  • ACTION_DRAG_LOCATION: una vez que el objeto de escucha recibe un evento ACTION_DRAG_ENTERED, recibe un nuevo ACTION_DRAG_LOCATION cada vez que se mueve el punto táctil hasta que recibe un evento ACTION_DRAG_EXITED. Los métodos getX() y getY() devuelve las coordenadas X e Y del punto táctil.
  • ACTION_DRAG_EXITED: este tipo de acción de evento se envía a un objeto de escucha que recibe ACTION_DRAG_ENTERED El evento se envía cuando el punto táctil de la sombra de arrastre se mueve desde el cuadro de límite del elemento View del objeto de escucha hacia afuera del cuadro de límite.

El objeto de escucha de eventos de arrastre no necesita reaccionar a ninguno de estos tipos de acciones. Si el objeto de escucha devuelve un valor al sistema; se ignora.

A continuación, se describen algunas pautas para responder a cada uno de estos tipos de acción:

  • En respuesta a ACTION_DRAG_ENTERED o ACTION_DRAG_LOCATION, el objeto de escucha puede cambiar el aspecto de View a fin de indicar que la vista es un posible destino de la acción de soltar.
  • Un evento con el tipo de acción ACTION_DRAG_LOCATION contiene datos válidos para getX() y getY(), que corresponden a la ubicación del punto táctil. El El objeto de escucha puede usar esta información para alterar el aspecto de View en el punto de contacto o para determinar la posición exacta donde el usuario puede soltar contenido.
  • En respuesta a ACTION_DRAG_EXITED, el objeto de escucha debe restablecer cualquier apariencia. cambios que se aplica en respuesta a ACTION_DRAG_ENTERED o ACTION_DRAG_LOCATION Esto le indica al usuario que el elemento View ya no es un destino inminente de la acción de soltar.

Responde a la acción de soltar

Cuando el usuario suelta la sombra de arrastre sobre un elemento View y el elemento View anterior informa que puede aceptar el contenido que se arrastra, el sistema despacha una arrastre el evento a View con el tipo de acción ACTION_DROP.

El objeto de escucha de eventos de arrastre debe hacer lo siguiente:

  1. Llama a getClipData() para obtener el objeto ClipData que se creó en un principio. proporcionados en la llamada para startDragAndDrop() y procesar los datos. Si la operación de arrastrar y soltar no representa datos movimiento, esto no es necesario.

  2. Muestra un valor booleano true para indicar que la acción de soltar se procesó correctamente. o false si no lo es. El valor mostrado se convierte en el valor que getResult() muestra para el evento ACTION_DRAG_ENDED final. Si el sistema no envía un evento ACTION_DROP, el valor que muestra getResult() para un evento de ACTION_DRAG_ENDED es false.

Para un evento ACTION_DROP, getX() y getY() usan el sistema de coordenadas de el objeto View que recibe la acción de soltar para mostrar las posiciones X e Y de la punto de contacto en el momento de la caída.

Mientras el usuario puede soltar la sombra de arrastre sobre un elemento View cuyo evento de arrastre el objeto de escucha no recibe eventos de arrastre, regiones vacías de la IU de tu app en áreas fuera de tu aplicación, Android no enviará un evento con acción escribe ACTION_DROP y solo enviará un evento ACTION_DRAG_ENDED.

Responde a la finalización de un arrastre

Inmediatamente después de que el usuario suelta la sombra de arrastre, el sistema envía un arrastre evento con un tipo de acción ACTION_DRAG_ENDED para todos los objetos de escucha de eventos de arrastre en tu aplicación. Esto indica que finalizó la operación de arrastre.

Cada objeto de escucha de eventos de arrastre debe hacer lo siguiente:

  1. Si el objeto de escucha cambia de apariencia durante la operación, se debe restablecer vuelve a su aspecto predeterminado como una indicación visual para el usuario de que el finalice la operación.
  2. De manera optativa, el objeto de escucha puede llamar a getResult() para obtener más información sobre la operación. Si un objeto de escucha muestra true en respuesta a un evento de acción. escribe ACTION_DROP, luego getResult() muestra un valor booleano true. En todas las demás casos, getResult() muestra un valor booleano false, incluso cuando el sistema no envía un evento ACTION_DROP.
  3. Para indicar que la operación de soltar se completó correctamente, el objeto de escucha debe mostrar un valor booleano true al sistema. Si no se muestra false, se genera un una señal visual que muestra que la sombra paralela regresa a su fuente puede sugerir al usuario que la operación no se realizó correctamente.

Cómo responder a eventos de arrastre: ejemplo

Todos los eventos de arrastre son recibidos inicialmente por el objeto de escucha o el método de evento de arrastre. El El siguiente fragmento de código es un ejemplo de respuesta a eventos de arrastre:

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;

});

Cómo personalizar una sombra de arrastre

Puedes definir un myDragShadowBuilder personalizado anulando los métodos en View.DragShadowBuilder Con el siguiente fragmento de código, se crea Sombra de arrastre rectangular y gris para un 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);
    }
}