Kéo và thả ở chế độ nhiều cửa sổ

Các thiết bị chạy Android 7.0 (API cấp 24) trở lên hỗ trợ chế độ nhiều cửa sổ, cho phép người dùng di chuyển dữ liệu từ ứng dụng này sang ứng dụng khác bằng cách kéo và thả.

Ứng dụng nguồn (nơi bắt đầu thao tác) sẽ cung cấp dữ liệu. Ứng dụng mục tiêu (nơi thao tác kết thúc) sẽ nhận được dữ liệu.

Khi người dùng bắt đầu kéo nội dung, ứng dụng nguồn phải đặt cờ DRAG_FLAG_GLOBAL để cho biết rằng người dùng có thể kéo dữ liệu sang một ứng dụng khác.

Vì dữ liệu di chuyển qua ranh giới ứng dụng, nên các ứng dụng chia sẻ quyền truy cập vào dữ liệu bằng cách sử dụng URI nội dung. Việc này yêu cầu:

  • Ứng dụng nguồn phải đặt một hoặc cả hai cờ DRAG_FLAG_GLOBAL_URI_READDRAG_FLAG_GLOBAL_URI_WRITE, tuỳ thuộc vào quyền đọc hoặc ghi dữ liệu mà ứng dụng nguồn muốn cấp cho ứng dụng đích.
  • Ứng dụng mục tiêu phải gọi requestDragAndDropPermissions() ngay trước khi xử lý dữ liệu mà người dùng kéo vào ứng dụng. Nếu ứng dụng mục tiêu không còn cần quyền truy cập vào dữ liệu đã kéo, thì ứng dụng có thể gọi release() trên đối tượng được trả về từ requestDragAndDropPermissions(). Nếu không, các quyền truy cập sẽ được huỷ khi hoạt động (activity) chứa bị huỷ bỏ. Nếu quá trình triển khai liên quan đến việc bắt đầu một Hoạt động mới để xử lý các URI bị bỏ qua, thì bạn cần phải cấp cho Hoạt động mới các quyền tương tự. Bạn phải đặt dữ liệu đoạn video và cờ:

    Kotlin

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

    Java

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

Các đoạn mã sau đây minh hoạ cách huỷ bỏ quyền chỉ có thể đọc cho dữ liệu được kéo ngay sau khi thao tác thả người dùng diễn ra. Hãy xem mẫu Kéo và thả trên GitHub để biết ví dụ hoàn chỉnh hơn.

Hoạt động nguồn

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();

Hoạt động mục tiêu

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;
});