Cómo arrastrar y soltar en el modo multiventana

Los dispositivos que ejecutan Android 7.0 (nivel de API 24) o versiones posteriores admiten el modo multiventana, que permite a los usuarios arrastrar y soltar datos de una app a otra.

La app de origen, donde comienza la operación, proporciona los datos. La app de destino, donde finaliza la operación, recibe los datos.

Cuando el usuario comienza a arrastrar contenido, la app de origen debe establecer la marca DRAG_FLAG_GLOBAL para indicar que el usuario puede arrastrar datos a otra app.

Debido a que los datos traspasan los límites de las apps, estas comparten el acceso a los datos mediante un URI de contenido. Para ello, debes hacer lo siguiente:

  • La app de origen debe establecer las marcas DRAG_FLAG_GLOBAL_URI_READ y DRAG_FLAG_GLOBAL_URI_WRITE, o ambas, según el acceso de lectura o escritura a los datos que la app de origen desee otorgarle a la app de destino.
  • La app de destino debe llamar a requestDragAndDropPermissions() inmediatamente antes de controlar los datos que el usuario arrastra a la app. Si la app de destino ya no necesita acceder a los datos arrastrados, la app puede llamar a release() en el objeto que se mostró desde requestDragAndDropPermissions(). De lo contrario, los permisos se liberan cuando se destruye la actividad que los contiene. Si tu implementación implica iniciar una actividad nueva para procesar los URI descartados, deberás otorgarle los mismos permisos a la actividad nueva. Debes configurar los datos del clip y una marca:

    Kotlin

    intent.setClipData(clipData)
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
    

    Java

    intent.setClipData(clipData);
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    

En los siguientes fragmentos de código, se muestra cómo otorgar acceso de solo lectura a los datos arrastrados inmediatamente después de que se realiza la operación de soltar el usuario. Consulta la muestra de arrastrar y soltar en GitHub para ver un ejemplo más completo.

Actividad de la fuente

Kotlin

// Drag a file stored in an images/ directory in internal storage.
val internalImagesDir = File(context.filesDir, "images")
val imageFile = File(internalImagesDir, imageFilename)
val uri = FileProvider.getUriForFile(context, contentAuthority, imageFile)

val listener = OnDragStartListener@{ view: View, _: DragStartHelper ->
    val clipData = ClipData(ClipDescription("Image Description",
                                            arrayOf("image/*")),
                            ClipData.Item(uri))
    // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps.
    // This example provides read-only access to the data.
    val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
    return@OnDragStartListener view.startDragAndDrop(clipData,
                                                     View.DragShadowBuilder(view),
                                                     null,
                                                     flags)
}

// Container where the image originally appears in the source app.
val srcImageView = findViewById<ImageView>(R.id.imageView)

// Detect and start the drag event.
DragStartHelper(srcImageView, listener).apply {
    attach()
}

Java

// Drag a file stored in an images/ directory in internal storage.
File internalImagesDir = new File(context.getFilesDir(), "images");
File imageFile = new File(internalImagesDir, imageFilename);
final Uri uri = FileProvider.getUriForFile(context, contentAuthority, imageFile);

// Container where the image originally appears in the source app.
ImageView srcImageView = findViewById(R.id.imageView);

// Enable the view to detect and start the drag event.
new DragStartHelper(srcImageView, (view, helper) -> {
    ClipData clipData = new ClipData(new ClipDescription("Image Description",
                                                          new String[] {"image/*"}),
                                     new ClipData.Item(uri));
    // Must include DRAG_FLAG_GLOBAL to permit dragging data between apps.
    // This example provides read-only access to the data.
    int flags = View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ;
    return view.startDragAndDrop(clipData,
                                 new View.DragShadowBuilder(view),
                                 null,
                                 flags);
}).attach();

Actividad objetivo

Kotlin

// Container where the image is to be dropped in the target app.
val targetImageView = findViewById<ImageView>(R.id.imageView)

targetImageView.setOnDragListener { view, event ->

    when (event.action) {

        ACTION_DROP -> {
            val imageItem: ClipData.Item = event.clipData.getItemAt(0)
            val uri = imageItem.uri

            // Request permission to access the image data being dragged into
            // the target activity's ImageView element.
            val dropPermissions = requestDragAndDropPermissions(event)
            (view as ImageView).setImageURI(uri)

            // Release the permission immediately afterward because it's no
            // longer needed.
            dropPermissions.release()
            return@setOnDragListener true
        }

        // Implement logic for other DragEvent cases here.

        // An unknown action type is received.
        else -> {
            Log.e("DragDrop Example", "Unknown action type received by View.OnDragListener.")
            return@setOnDragListener false
        }

    }
}

Java

// Container where the image is to be dropped in the target app.
ImageView targetImageView = findViewById(R.id.imageView);

targetImageView.setOnDragListener( (view, event) -> {

    switch (event.getAction()) {

        case ACTION_DROP:
            ClipData.Item imageItem = event.getClipData().getItemAt(0);
            Uri uri = imageItem.getUri();

            // Request permission to access the image data being dragged into
            // the target activity's ImageView element.
            DragAndDropPermissions dropPermissions =
                requestDragAndDropPermissions(event);

            ((ImageView)view).setImageURI(uri);

            // Release the permission immediately afterward because it's no
            // longer needed.
            dropPermissions.release();

            return true;

        // Implement logic for other DragEvent cases here.

        // An unknown action type was received.
        default:
            Log.e("DragDrop Example","Unknown action type received by View.OnDragListener.");
            break;
    }

    return false;
});