Skip to content

Commit ab694a1

Browse files
Merge pull request #163 from android/dnd
Drag and Drop samples update
2 parents d65c1d5 + a063171 commit ab694a1

13 files changed

+910
-1
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: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,14 @@ This sample demonstrates displaying an UltraHDR image in a Compose View and an A
3434
Download fonts instead of bundling them in the app resources.
3535
- [Drag and Drop](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDrop.kt):
3636
Demonstrates basic Drag and Drop functionality.
37+
- [Drag and Drop - Helper](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropWithHelper.kt):
38+
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
41+
- [Drag and Drop in MultiWindow mode](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropMultiWindow.kt):
42+
Drag and drop to another app visible in multiwindow mode
43+
- [Drag and Drop using the RichContentReceiver](user-interface/draganddrop/src/main/java/com/example/platform/ui/draganddrop/DragAndDropRichContentReceiverFragment.kt):
44+
Using RichContentReceiverInterface for implementing Drop for rich data types
3745
- [Editing UltraHDR](graphics/ultrahdr/src/main/java/com/example/platform/graphics/ultrahdr/edit/EditingUltraHDR.kt):
3846
This sample demonstrates editing an UltraHDR image and the resulting gainmap as well. Spatial edit operations like crop, rotate, scale are supported
3947
- [Find devices sample](connectivity/bluetooth/ble/src/main/java/com/example/platform/connectivity/bluetooth/ble/FindBLEDevicesSample.kt):

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
plugins {
1818
id("com.example.platform.sample")
1919
}
20-
20+
2121
android {
2222
namespace = "com.example.platform.ui.draganddrop"
2323
buildFeatures {
@@ -29,4 +29,9 @@ dependencies {
2929
implementation(libs.material)
3030
implementation(libs.androidx.constraintlayout)
3131
implementation(libs.androidx.draganddrop)
32+
implementation(libs.glide)
33+
implementation(libs.androidx.media3.common)
34+
implementation(libs.androidx.media3.ui)
35+
implementation(libs.androidx.media3.exoplayer)
36+
implementation(libs.glide.compose)
3237
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 android.os.Build
22+
import android.os.Bundle
23+
import android.view.View
24+
import android.widget.ImageView
25+
import androidx.annotation.RequiresApi
26+
import androidx.constraintlayout.widget.ConstraintSet
27+
import androidx.core.util.component1
28+
import androidx.core.util.component2
29+
import androidx.core.view.ContentInfoCompat
30+
import androidx.core.view.DragStartHelper
31+
import androidx.draganddrop.DropHelper
32+
import androidx.fragment.app.Fragment
33+
import com.bumptech.glide.Glide
34+
import com.example.platform.ui.draganddrop.databinding.FragmentDndMultiwindowBinding
35+
import com.google.android.catalog.framework.annotations.Sample
36+
37+
@Sample(
38+
name = "Drag and Drop in MultiWindow mode",
39+
description = "Drag and drop to another app visible in multiwindow mode",
40+
documentation = "",
41+
)
42+
class DragAndDropMultiWindow : Fragment(R.layout.fragment_dnd_multiwindow) {
43+
lateinit var binding: FragmentDndMultiwindowBinding
44+
45+
@RequiresApi(Build.VERSION_CODES.N)
46+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
47+
super.onViewCreated(view, savedInstanceState)
48+
binding = FragmentDndMultiwindowBinding.bind(view)
49+
ConstraintSet().clone(binding.root)
50+
binding.tvGreeting.text = resources.getString(R.string.dnd_multiwindow_greeting)
51+
binding.ivSource.tag = resources.getString(R.string.source_image_url)
52+
Glide.with(this).asBitmap().load(resources.getString(R.string.source_image_url))
53+
.into(binding.ivSource)
54+
Glide.with(this).asBitmap().load(resources.getString(R.string.target_image_url))
55+
.into(binding.ivTarget)
56+
setupDrag(binding.ivSource)
57+
setupDrop(binding.ivTarget)
58+
59+
}
60+
61+
@RequiresApi(Build.VERSION_CODES.N)
62+
private fun setupDrag(draggableView: ImageView) {
63+
DragStartHelper(draggableView) { view: View, _: DragStartHelper ->
64+
val item = ClipData.Item(view.tag as? CharSequence)
65+
val dragData = ClipData(
66+
view.tag as? CharSequence,
67+
arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
68+
item,
69+
)
70+
// Flag is required so that data from clipdata can be read by the drop target.
71+
// view can directly specify the flags in this helper method.
72+
val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
73+
view.startDragAndDrop(
74+
dragData,
75+
View.DragShadowBuilder(view),
76+
null,
77+
flags,
78+
)
79+
}.attach()
80+
}
81+
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(
90+
requireActivity(),
91+
targetView,
92+
arrayOf("text/*"),
93+
) { _, payload: ContentInfoCompat ->
94+
val item = payload.clip.getItemAt(0)
95+
val dragData = item.text
96+
Glide.with(this).load(dragData).centerCrop().into(targetView)
97+
val (_, remaining) = payload.partition { it == item }
98+
remaining
99+
}
100+
}
101+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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.graphics.Bitmap
21+
import android.os.Build
22+
import android.os.Bundle
23+
import android.util.Log
24+
import android.view.View
25+
import android.widget.ImageView
26+
import android.widget.TextView
27+
import androidx.annotation.RequiresApi
28+
import androidx.constraintlayout.widget.ConstraintSet
29+
import androidx.core.content.ContextCompat
30+
import androidx.core.content.FileProvider
31+
import androidx.core.graphics.drawable.toBitmap
32+
import androidx.core.util.component1
33+
import androidx.core.util.component2
34+
import androidx.core.view.ContentInfoCompat
35+
import androidx.core.view.DragStartHelper
36+
import androidx.draganddrop.DropHelper
37+
import androidx.fragment.app.Fragment
38+
import androidx.recyclerview.widget.LinearLayoutManager
39+
import com.bumptech.glide.Glide
40+
import com.example.platform.ui.draganddrop.databinding.FragmentDndRichcontentBinding
41+
import com.google.android.catalog.framework.annotations.Sample
42+
import java.io.ByteArrayOutputStream
43+
import java.io.File
44+
import java.io.FileOutputStream
45+
46+
47+
@Sample(
48+
name = "Drag and Drop using the RichContentReceiver",
49+
description = "Using RichContentReceiverInterface for implementing Drop for rich data types",
50+
documentation = "https://developer.android.com/develop/ui/views/receive-rich-content",
51+
)
52+
class DragAndDropRichContentReceiverFragment : Fragment(R.layout.fragment_dnd_richcontent) {
53+
private val TAG = DragAndDropRichContentReceiverFragment::class.java.simpleName
54+
private lateinit var binding: FragmentDndRichcontentBinding
55+
56+
@RequiresApi(Build.VERSION_CODES.S)
57+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
58+
super.onViewCreated(view, savedInstanceState)
59+
binding = FragmentDndRichcontentBinding.bind(view)
60+
ConstraintSet().clone(binding.root)
61+
val items = mutableListOf<GridItem>()
62+
initViews(items)
63+
initDrag()
64+
65+
// setOnReceiveContentListener accepts data from various input sources
66+
// based on the type of view.
67+
// here Target View is made to handle Drag and Drop Events
68+
binding.rvDropTarget.setOnReceiveContentListener(
69+
arrayOf("image/*", "text/*", "video/*"),
70+
) { _, payload ->
71+
val (uriContent, remaining) = ContentInfoCompat.partition(
72+
payload,
73+
) { item: ClipData.Item -> item.uri != null }
74+
75+
if (uriContent != null) {
76+
val item = uriContent.clip.getItemAt(0)
77+
item.uri?.let { uri ->
78+
val size = 160.px
79+
decodeSampledBitmapFromUri(
80+
requireActivity().contentResolver,
81+
uri,
82+
size,
83+
size,
84+
)?.let { bitmap ->
85+
items.add(GridItem("image/*", uri.toString()))
86+
(binding.rvDropTarget.adapter as RichContentReceiverAdapter).notifyItemInserted(
87+
items.size - 1,
88+
)
89+
}
90+
} ?: run {
91+
Log.e(TAG, "Clip data is missing URI")
92+
}
93+
}
94+
if (remaining != null) {
95+
val item = remaining.clip.getItemAt(0)
96+
items.add(GridItem("text/*", item.text.toString()))
97+
(binding.rvDropTarget.adapter as RichContentReceiverAdapter).notifyItemInserted(
98+
items.size - 1,
99+
)
100+
101+
}
102+
null
103+
}
104+
}
105+
106+
private fun initViews(items: MutableList<GridItem>) {
107+
val layoutManager = LinearLayoutManager(requireContext())
108+
layoutManager.orientation = LinearLayoutManager.VERTICAL
109+
binding.rvDropTarget.layoutManager = layoutManager
110+
val adapter = RichContentReceiverAdapter(items)
111+
binding.rvDropTarget.adapter = adapter
112+
113+
//using DropHelper to define shadow for drop area
114+
DropHelper.configureView(
115+
requireActivity(),
116+
binding.rvDropTarget,
117+
arrayOf("image/*", "text/*", "video/*"),
118+
DropHelper.Options.Builder()
119+
.setHighlightColor(ContextCompat.getColor(requireContext(), R.color.purple_300))
120+
.setHighlightCornerRadiusPx(16.px).build(),
121+
) { _, payload: ContentInfoCompat ->
122+
payload
123+
}
124+
}
125+
126+
@RequiresApi(Build.VERSION_CODES.N)
127+
fun initDrag() {
128+
Glide.with(this).asBitmap().load(getString(R.string.target_image_url))
129+
.into(binding.ivSource1)
130+
addDragStartHelperForImageView(binding.ivSource1)
131+
Glide.with(this).asBitmap().load(getString(R.string.target_image_url1))
132+
.into(binding.ivSource)
133+
addDragStartHelperForImageView(binding.ivSource)
134+
135+
binding.tvDrag.text = getString(R.string.dnd_helper_greeting)
136+
addDragStartHelperForTextView(binding.tvDrag)
137+
138+
}
139+
140+
@RequiresApi(Build.VERSION_CODES.N)
141+
fun addDragStartHelperForTextView(draggbleView: View) {
142+
DragStartHelper(draggbleView) { view: View, _: DragStartHelper ->
143+
val dragData = ClipData.newPlainText("DraggedText", (view as TextView).text)
144+
val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
145+
view.startDragAndDrop(
146+
dragData,
147+
View.DragShadowBuilder(view),
148+
null,
149+
flags,
150+
)
151+
}.attach()
152+
}
153+
154+
@RequiresApi(Build.VERSION_CODES.N)
155+
fun addDragStartHelperForImageView(draggableView: View) {
156+
DragStartHelper(draggableView) { view: View, _: DragStartHelper ->
157+
val filename = System.currentTimeMillis().toString()
158+
val imageFile = File(File(requireActivity().filesDir, "images"), filename + ".png")
159+
ByteArrayOutputStream().use { bos ->
160+
(view as ImageView).drawable.toBitmap().compress(Bitmap.CompressFormat.PNG, 0, bos)
161+
FileOutputStream(imageFile).use { fos ->
162+
fos.write(bos.toByteArray())
163+
fos.flush()
164+
}
165+
}
166+
val imageUri = FileProvider.getUriForFile(
167+
requireContext(),
168+
"${requireActivity().packageName}.draganddrop.provider",
169+
imageFile,
170+
)
171+
val dragData = ClipData.newUri(
172+
requireActivity().contentResolver,
173+
"Image",
174+
imageUri,
175+
)
176+
val flags = View.DRAG_FLAG_GLOBAL or View.DRAG_FLAG_GLOBAL_URI_READ
177+
view.startDragAndDrop(
178+
dragData,
179+
View.DragShadowBuilder(view),
180+
null,
181+
flags,
182+
)
183+
}.attach()
184+
}
185+
}

0 commit comments

Comments
 (0)