Skip to content

Commit a063171

Browse files
committed
Drag and Drop Compose
Change-Id: I4fa2a9b3d1d56fa1773b52c2cf7d3ad6f0d49d16
1 parent b720b8b commit a063171

File tree

6 files changed

+205
-38
lines changed

6 files changed

+205
-38
lines changed

gradle/libs.versions.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ media3 = "1.2.1"
4242
appcompat = "1.6.1"
4343
material = "1.12.0-beta01"
4444
constraintlayout = "2.1.4"
45+
glide-compose = "1.0.0-beta01"
4546

4647
[libraries]
4748

@@ -145,6 +146,8 @@ androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "medi
145146
fresco = "com.facebook.fresco:fresco:3.0.0"
146147
fresco-nativeimagetranscoder = "com.facebook.fresco:nativeimagetranscoder:2.6.0!!"
147148
glide = "com.github.bumptech.glide:glide:4.15.1"
149+
glide-compose = { group = "com.github.bumptech.glide", name = "compose", version.ref = "glide-compose" }
150+
148151

149152
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
150153
material = { group = "com.google.android.material", name = "material", version.ref = "material" }

samples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ Download fonts instead of bundling them in the app resources.
3636
Demonstrates basic Drag and Drop functionality.
3737
- [Drag and Drop - Helper](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropWithHelper.kt):
3838
Drag and Drop using the DragHelper and DropHelper from DragAndDropHelper library
39+
- [Drag and Drop in Compose](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropUsingCompose.kt):
40+
Drag and drop in Compose
3941
- [Drag and Drop in MultiWindow mode](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropMultiWindow.kt):
4042
Drag and drop to another app visible in multiwindow mode
4143
- [Drag and Drop using the RichContentReceiver](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropRichContentReceiverFragment.kt):

samples/user-interface/draganddrop/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,5 @@ dependencies {
3333
implementation(libs.androidx.media3.common)
3434
implementation(libs.androidx.media3.ui)
3535
implementation(libs.androidx.media3.exoplayer)
36+
implementation(libs.glide.compose)
3637
}

samples/user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropMultiWindow.kt

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ class DragAndDropMultiWindow : Fragment(R.layout.fragment_dnd_multiwindow) {
6060

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

85-
private fun setupDrop(targetView: ImageView) {
86-
// DropHelper manages permission to read data across app, provided DRAG permission has been
87-
// granted by drag source. No additional code is required as transient permission
88-
// are granted and released
89-
// Consider using [WorkManager] if processing of clipdata is long-running
90-
// if you are setting up [onDragListener] , you have to add ask for permission before
91-
// handling the data and release permissions once done with it.
92-
// e.g.
93-
// val dropPermissions = requestDragAndDropPermissions()
94-
// -- handle the clipdata
95-
// dropPermissions.release()
96-
// Please refer [https://developer.android.com/develop/ui/views/touch-and-input/drag-drop#DragPermissionsMultiWindow]
97-
// for more details
98-
DropHelper.configureView(
82+
private fun setupDrop(targetView: ImageView) {
83+
/**
84+
* DropHelper manages permission to read data across app, provided DRAG permission has been
85+
* granted by drag source. No additional code is required as transient permission
86+
* are granted and released
87+
* Consider performing processing of [ClipData] in background if it is long-running
88+
*/
89+
DropHelper.configureView(
9990
requireActivity(),
10091
targetView,
10192
arrayOf("text/*"),
10293
) { _, payload: ContentInfoCompat ->
10394
val item = payload.clip.getItemAt(0)
10495
val dragData = item.text
105-
Glide.with(this)
106-
.load(dragData)
107-
.centerCrop().into(targetView)
96+
Glide.with(this).load(dragData).centerCrop().into(targetView)
10897
val (_, remaining) = payload.partition { it == item }
10998
remaining
11099
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
/*
2+
* Copyright 2023 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.platform.ui.draganddrop
18+
19+
import android.content.ClipData
20+
import android.content.ClipDescription
21+
import androidx.compose.foundation.ExperimentalFoundationApi
22+
import androidx.compose.foundation.background
23+
import androidx.compose.foundation.draganddrop.dragAndDropSource
24+
import androidx.compose.foundation.draganddrop.dragAndDropTarget
25+
import androidx.compose.foundation.gestures.detectTapGestures
26+
import androidx.compose.foundation.layout.Arrangement
27+
import androidx.compose.foundation.layout.Box
28+
import androidx.compose.foundation.layout.Column
29+
import androidx.compose.foundation.layout.fillMaxSize
30+
import androidx.compose.foundation.layout.fillMaxWidth
31+
import androidx.compose.foundation.layout.padding
32+
import androidx.compose.foundation.layout.size
33+
import androidx.compose.foundation.lazy.grid.GridCells
34+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
35+
import androidx.compose.material3.HorizontalDivider
36+
import androidx.compose.runtime.Composable
37+
import androidx.compose.runtime.getValue
38+
import androidx.compose.runtime.mutableStateListOf
39+
import androidx.compose.runtime.mutableStateOf
40+
import androidx.compose.runtime.remember
41+
import androidx.compose.runtime.setValue
42+
import androidx.compose.ui.Modifier
43+
import androidx.compose.ui.draganddrop.DragAndDropEvent
44+
import androidx.compose.ui.draganddrop.DragAndDropTarget
45+
import androidx.compose.ui.draganddrop.DragAndDropTransferData
46+
import androidx.compose.ui.draganddrop.mimeTypes
47+
import androidx.compose.ui.draganddrop.toAndroidDragEvent
48+
import androidx.compose.ui.graphics.Color
49+
import androidx.compose.ui.unit.dp
50+
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
51+
import com.bumptech.glide.integration.compose.GlideImage
52+
import com.google.android.catalog.framework.annotations.Sample
53+
54+
@Sample(
55+
name = "Drag and Drop in Compose",
56+
description = "Drag and drop in Compose",
57+
documentation = "",
58+
)
59+
@Composable
60+
fun DragAndDropCompose() {
61+
Column(
62+
modifier = Modifier
63+
.fillMaxSize()
64+
.padding(8.dp),
65+
) {
66+
DragBox(Modifier.weight(1f))
67+
HorizontalDivider()
68+
DropBox(Modifier.weight(2f))
69+
}
70+
}
71+
72+
@OptIn(ExperimentalFoundationApi::class)
73+
@Composable
74+
fun DropBox(modifier: Modifier) {
75+
val targetPhotoUrls = remember {
76+
mutableStateListOf<String>()
77+
}
78+
var backgroundColor by remember {
79+
mutableStateOf(Color(0xffE5E4E2))
80+
}
81+
val dragAndDropTarget = remember {
82+
object : DragAndDropTarget {
83+
override fun onDrop(event: DragAndDropEvent): Boolean {
84+
val data = event.toAndroidDragEvent().clipData.getItemAt(0).text
85+
targetPhotoUrls.add(data.toString())
86+
return true
87+
}
88+
89+
override fun onEntered(event: DragAndDropEvent) {
90+
super.onEntered(event)
91+
backgroundColor = Color(0xffD3D3D3)
92+
}
93+
94+
override fun onEnded(event: DragAndDropEvent) {
95+
super.onExited(event)
96+
backgroundColor = Color(0xffE5E4E2)
97+
}
98+
99+
override fun onExited(event: DragAndDropEvent) {
100+
super.onExited(event)
101+
backgroundColor = Color(0xffE5E4E2)
102+
}
103+
}
104+
}
105+
Box(
106+
modifier = modifier
107+
.fillMaxWidth()
108+
.padding(8.dp)
109+
.background(color = backgroundColor)
110+
.dragAndDropTarget(
111+
shouldStartDragAndDrop = { event ->
112+
event
113+
.mimeTypes()
114+
.contains(ClipDescription.MIMETYPE_TEXT_PLAIN)
115+
},
116+
target = dragAndDropTarget,
117+
),
118+
) {
119+
LazyVerticalGrid(
120+
columns = GridCells.Adaptive(minSize = 100.dp),
121+
verticalArrangement = Arrangement.spacedBy(10.dp),
122+
horizontalArrangement = Arrangement.spacedBy(10.dp),
123+
) {
124+
items(targetPhotoUrls.size) { index ->
125+
Photo(targetPhotoUrls[index])
126+
}
127+
128+
}
129+
}
130+
}
131+
132+
@Composable
133+
fun DragBox(modifier: Modifier) {
134+
Box(
135+
modifier = modifier.fillMaxWidth(),
136+
) {
137+
val sourcePhotoUrls = remember {
138+
mutableStateListOf(
139+
"https://services.google.com/fh/files/misc/qq10.jpeg",
140+
"https://services.google.com/fh/files/misc/qq9.jpeg",
141+
"https://services.google.com/fh/files/misc/qq8.jpeg",
142+
)
143+
}
144+
LazyVerticalGrid(
145+
columns = GridCells.Adaptive(minSize = 100.dp),
146+
verticalArrangement = Arrangement.spacedBy(10.dp),
147+
horizontalArrangement = Arrangement.spacedBy(10.dp),
148+
) {
149+
items(sourcePhotoUrls.size) { index ->
150+
Photo(sourcePhotoUrls[index])
151+
}
152+
153+
}
154+
}
155+
}
156+
157+
@OptIn(ExperimentalGlideComposeApi::class, ExperimentalFoundationApi::class)
158+
@Composable
159+
fun Photo(urlStr: String) {
160+
val url by remember {
161+
mutableStateOf(urlStr)
162+
}
163+
GlideImage(
164+
model = url,
165+
contentDescription = "demo",
166+
modifier = Modifier
167+
.size(100.dp)
168+
.dragAndDropSource {
169+
detectTapGestures(
170+
onLongPress = {
171+
startTransfer(
172+
DragAndDropTransferData(
173+
ClipData.newPlainText(
174+
"image Url", url,
175+
),
176+
),
177+
)
178+
},
179+
)
180+
},
181+
)
182+
}

samples/user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropWithHelper.kt

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,16 +71,11 @@ class DragAndDropWithHelper : Fragment(R.layout.fragment_drag_and_drop_with_help
7171
}
7272

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

99-
private fun setupDrop(targetView: ImageView) {
100-
/*
94+
private fun setupDrop(targetView: ImageView) {/*
10195
Similar to drag method, drop in normal way be handled with implementing the listener for
102-
Drop event, and handling each DropEvent as showcased here
103-
https://developer.android.com/develop/ui/views/touch-and-input/drag-drop#RespondEventSample
104-
DropHelper provides the utility method for the ease of this implementation
96+
Drop event,
10597
*/
10698
DropHelper.configureView(
10799
requireActivity(),
@@ -110,9 +102,7 @@ class DragAndDropWithHelper : Fragment(R.layout.fragment_drag_and_drop_with_help
110102
) { _, payload: ContentInfoCompat ->
111103
val item = payload.clip.getItemAt(0)
112104
val dragData = item.text
113-
Glide.with(this)
114-
.load(dragData)
115-
.centerCrop().into(targetView)
105+
Glide.with(this).load(dragData).centerCrop().into(targetView)
116106
val (_, remaining) = payload.partition { it == item }
117107
remaining
118108
}

0 commit comments

Comments
 (0)