멀티 윈도우 모드에서 드래그 앤 드롭

Android 7.0 (API 수준 24) 이상을 실행하는 기기에서는 사용자가 드래그 앤 드롭을 통해 한 앱에서 다른 앱으로 데이터를 이동할 수 있는 멀티 윈도우 모드를 지원합니다.

작업이 시작되는 소스 앱에서 데이터를 제공합니다. 작업이 종료되는 타겟 앱에서 데이터를 수신합니다.

사용자가 콘텐츠를 드래그하기 시작하면 소스 앱은 DRAG_FLAG_GLOBAL 플래그를 설정하여 사용자가 데이터를 다른 앱으로 드래그할 수 있음을 나타내야 합니다.

데이터는 앱 경계를 넘어 이동하므로 앱은 콘텐츠 URI를 사용하여 데이터 액세스를 공유합니다. 이를 위해서는 다음이 필요합니다.

  • 소스 앱은 소스 앱이 타겟 앱에 부여하려는 데이터에 관한 읽기 또는 쓰기 액세스 권한에 따라 DRAG_FLAG_GLOBAL_URI_READ 플래그와 DRAG_FLAG_GLOBAL_URI_WRITE 플래그 중 하나 또는 둘 다를 설정해야 합니다.
  • 타겟 앱은 사용자가 앱으로 드래그하는 데이터를 처리하기 직전에 requestDragAndDropPermissions()를 호출해야 합니다. 타겟 앱이 더 이상 드래그된 데이터에 액세스할 필요가 없다면 앱은 requestDragAndDropPermissions()에서 반환된 객체에서 release()를 호출할 수 있습니다. 그렇게 하지 않으면 포함하는 활동이 소멸될 때 권한이 해제됩니다. 구현 시 삭제된 URI를 처리하기 위해 새 활동을 시작하는 경우 새 활동에 동일한 권한을 부여해야 합니다. 클립 데이터와 플래그를 설정해야 합니다.

    Kotlin

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

    Java

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

다음 코드 스니펫은 사용자 드롭 작업이 실행된 직후 드래그된 데이터에 대한 읽기 전용 액세스 권한을 해제하는 방법을 보여줍니다. 전체 예는 GitHub의 드래그 앤 드롭 샘플을 참고하세요.

소스 활동

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

목표 활동

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