Skip to content

Commit

Permalink
Drag and Drop Compose
Browse files Browse the repository at this point in the history
Change-Id: I4fa2a9b3d1d56fa1773b52c2cf7d3ad6f0d49d16
  • Loading branch information
satishshendeg committed May 2, 2024
1 parent b720b8b commit a063171
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 38 deletions.
3 changes: 3 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ media3 = "1.2.1"
appcompat = "1.6.1"
material = "1.12.0-beta01"
constraintlayout = "2.1.4"
glide-compose = "1.0.0-beta01"

[libraries]

Expand Down Expand Up @@ -145,6 +146,8 @@ androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "medi
fresco = "com.facebook.fresco:fresco:3.0.0"
fresco-nativeimagetranscoder = "com.facebook.fresco:nativeimagetranscoder:2.6.0!!"
glide = "com.github.bumptech.glide:glide:4.15.1"
glide-compose = { group = "com.github.bumptech.glide", name = "compose", version.ref = "glide-compose" }


appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
Expand Down
2 changes: 2 additions & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Download fonts instead of bundling them in the app resources.
Demonstrates basic Drag and Drop functionality.
- [Drag and Drop - Helper](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropWithHelper.kt):
Drag and Drop using the DragHelper and DropHelper from DragAndDropHelper library
- [Drag and Drop in Compose](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropUsingCompose.kt):
Drag and drop in Compose
- [Drag and Drop in MultiWindow mode](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropMultiWindow.kt):
Drag and drop to another app visible in multiwindow mode
- [Drag and Drop using the RichContentReceiver](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropRichContentReceiverFragment.kt):
Expand Down
1 change: 1 addition & 0 deletions samples/user-interface/draganddrop/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ dependencies {
implementation(libs.androidx.media3.common)
implementation(libs.androidx.media3.ui)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.glide.compose)
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ class DragAndDropMultiWindow : Fragment(R.layout.fragment_dnd_multiwindow) {

@RequiresApi(Build.VERSION_CODES.N)
private fun setupDrag(draggableView: ImageView) {
DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
DragStartHelper(draggableView) { view: View, _: DragStartHelper ->
val item = ClipData.Item(view.tag as? CharSequence)
val dragData = ClipData(
view.tag as? CharSequence,
Expand All @@ -70,8 +69,6 @@ class DragAndDropMultiWindow : Fragment(R.layout.fragment_dnd_multiwindow) {
)
// Flag is required so that data from clipdata can be read by the drop target.
// view can directly specify the flags in this helper method.
// additionally if you are using [View.startDragAndDrop] for drag implementation
// you can set the flags in similar fashion
val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
view.startDragAndDrop(
dragData,
Expand All @@ -82,29 +79,21 @@ class DragAndDropMultiWindow : Fragment(R.layout.fragment_dnd_multiwindow) {
}.attach()
}

private fun setupDrop(targetView: ImageView) {
// DropHelper manages permission to read data across app, provided DRAG permission has been
// granted by drag source. No additional code is required as transient permission
// are granted and released
// Consider using [WorkManager] if processing of clipdata is long-running
// if you are setting up [onDragListener] , you have to add ask for permission before
// handling the data and release permissions once done with it.
// e.g.
// val dropPermissions = requestDragAndDropPermissions()
// -- handle the clipdata
// dropPermissions.release()
// Please refer [https://developer.android.com/develop/ui/views/touch-and-input/drag-drop#DragPermissionsMultiWindow]
// for more details
DropHelper.configureView(
private fun setupDrop(targetView: ImageView) {
/**
* DropHelper manages permission to read data across app, provided DRAG permission has been
* granted by drag source. No additional code is required as transient permission
* are granted and released
* Consider performing processing of [ClipData] in background if it is long-running
*/
DropHelper.configureView(
requireActivity(),
targetView,
arrayOf("text/*"),
) { _, payload: ContentInfoCompat ->
val item = payload.clip.getItemAt(0)
val dragData = item.text
Glide.with(this)
.load(dragData)
.centerCrop().into(targetView)
Glide.with(this).load(dragData).centerCrop().into(targetView)
val (_, remaining) = payload.partition { it == item }
remaining
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.platform.ui.draganddrop

import android.content.ClipData
import android.content.ClipDescription
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.draganddrop.dragAndDropSource
import androidx.compose.foundation.draganddrop.dragAndDropTarget
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draganddrop.DragAndDropEvent
import androidx.compose.ui.draganddrop.DragAndDropTarget
import androidx.compose.ui.draganddrop.DragAndDropTransferData
import androidx.compose.ui.draganddrop.mimeTypes
import androidx.compose.ui.draganddrop.toAndroidDragEvent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage
import com.google.android.catalog.framework.annotations.Sample

@Sample(
name = "Drag and Drop in Compose",
description = "Drag and drop in Compose",
documentation = "",
)
@Composable
fun DragAndDropCompose() {
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp),
) {
DragBox(Modifier.weight(1f))
HorizontalDivider()
DropBox(Modifier.weight(2f))
}
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun DropBox(modifier: Modifier) {
val targetPhotoUrls = remember {
mutableStateListOf<String>()
}
var backgroundColor by remember {
mutableStateOf(Color(0xffE5E4E2))
}
val dragAndDropTarget = remember {
object : DragAndDropTarget {
override fun onDrop(event: DragAndDropEvent): Boolean {
val data = event.toAndroidDragEvent().clipData.getItemAt(0).text
targetPhotoUrls.add(data.toString())
return true
}

override fun onEntered(event: DragAndDropEvent) {
super.onEntered(event)
backgroundColor = Color(0xffD3D3D3)
}

override fun onEnded(event: DragAndDropEvent) {
super.onExited(event)
backgroundColor = Color(0xffE5E4E2)
}

override fun onExited(event: DragAndDropEvent) {
super.onExited(event)
backgroundColor = Color(0xffE5E4E2)
}
}
}
Box(
modifier = modifier
.fillMaxWidth()
.padding(8.dp)
.background(color = backgroundColor)
.dragAndDropTarget(
shouldStartDragAndDrop = { event ->
event
.mimeTypes()
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
},
target = dragAndDropTarget,
),
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 100.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
items(targetPhotoUrls.size) { index ->
Photo(targetPhotoUrls[index])
}

}
}
}

@Composable
fun DragBox(modifier: Modifier) {
Box(
modifier = modifier.fillMaxWidth(),
) {
val sourcePhotoUrls = remember {
mutableStateListOf(
"https://services.google.com/fh/files/misc/qq10.jpeg",
"https://services.google.com/fh/files/misc/qq9.jpeg",
"https://services.google.com/fh/files/misc/qq8.jpeg",
)
}
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 100.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
items(sourcePhotoUrls.size) { index ->
Photo(sourcePhotoUrls[index])
}

}
}
}

@OptIn(ExperimentalGlideComposeApi::class, ExperimentalFoundationApi::class)
@Composable
fun Photo(urlStr: String) {
val url by remember {
mutableStateOf(urlStr)
}
GlideImage(
model = url,
contentDescription = "demo",
modifier = Modifier
.size(100.dp)
.dragAndDropSource {
detectTapGestures(
onLongPress = {
startTransfer(
DragAndDropTransferData(
ClipData.newPlainText(
"image Url", url,
),
),
)
},
)
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,11 @@ class DragAndDropWithHelper : Fragment(R.layout.fragment_drag_and_drop_with_help
}

@RequiresApi(Build.VERSION_CODES.N)
private fun setupDrag(draggableView: ImageView) {
/*
Usual way of implementing the drag would involve the use of setLongClickListener method ,
defining clipdata , building shadow and starting the drag as showcased in
https://developer.android.com/develop/ui/views/touch-and-input/drag-drop#StartDrag
DragStartHelper provide the utility method to avoid this boilerplate code and prove ease of
implementation for dragging the view.
private fun setupDrag(draggableView: ImageView) {/*
DragStartHelper is a utility class for implementing drag and drop support
DragStartHelper provide the ease of implementation for dragging the view.
*/
DragStartHelper(draggableView)
{ view: View, _: DragStartHelper ->
DragStartHelper(draggableView) { view: View, _: DragStartHelper ->
val item = ClipData.Item(view.tag as? CharSequence)
val dragData = ClipData(
view.tag as? CharSequence,
Expand All @@ -96,12 +91,9 @@ class DragAndDropWithHelper : Fragment(R.layout.fragment_drag_and_drop_with_help
}.attach()
}

private fun setupDrop(targetView: ImageView) {
/*
private fun setupDrop(targetView: ImageView) {/*
Similar to drag method, drop in normal way be handled with implementing the listener for
Drop event, and handling each DropEvent as showcased here
https://developer.android.com/develop/ui/views/touch-and-input/drag-drop#RespondEventSample
DropHelper provides the utility method for the ease of this implementation
Drop event,
*/
DropHelper.configureView(
requireActivity(),
Expand All @@ -110,9 +102,7 @@ class DragAndDropWithHelper : Fragment(R.layout.fragment_drag_and_drop_with_help
) { _, payload: ContentInfoCompat ->
val item = payload.clip.getItemAt(0)
val dragData = item.text
Glide.with(this)
.load(dragData)
.centerCrop().into(targetView)
Glide.with(this).load(dragData).centerCrop().into(targetView)
val (_, remaining) = payload.partition { it == item }
remaining
}
Expand Down

0 comments on commit a063171

Please sign in to comment.