Hello Android developers,
Foldable and large-screen devices are great for multi-tasking – you can position two apps side-by-side to compare data or just do two things at once! The other benefit of side-by-side apps is the ability to drag and drop content between them, whether the apps are across screens on a dual-screen device, or next to each other in multi-window mode on a tablet device.
The Surface Duo drag and drop sample has been updated to use the AndroidX drag and drop package to show how to add this feature to your apps. This package provides helper methods to simplify drag and drop implementation.
The Surface Duo SDK samples GitHub repo includes a drag-and-drop demo that works both within the sample app as well as dragging to other apps. Here’s a screenshot of the app spanned across both screens – the image and text can be dragged and dropped into the container targets.
To use the AndroidX drag and drop helpers, add the following to the module’s build.gradle file dependencies:
implementation 'androidx.draganddrop:draganddrop:1.0.0'
More information on AndroidX drag and drop is in the release notes and docs. Google also provides detailed guidance for implementing drag and drop that explains implementations with built-in Android APIs as well as using the AndroidX helper package.
Drag and drop text
The simplest element to drag and drop is plain text. The DragStartHelper class is attached to a view, and will detect common drag gestures (eg. long click), which helps track the drag on screen (including the shadow).
The sample implements DragStartHelper
for the text view in DragSourceFragment.onViewCreated
:
DragStartHelper(binding.dragTextView) { sourceView, _ -> val text = (sourceView as TextView).text val dragClipData = ClipData.newPlainText(ClipDescription.MIMETYPE_TEXT_PLAIN, text) val dragShadowBuilder = View.DragShadowBuilder(sourceView) sourceView.startDragAndDrop(dragClipData, dragShadowBuilder, null, DRAG_FLAG_GLOBAL) }.attach()
Text content can be passed directly in the drag message so the clip data object that’s created contains the text and the method to accept the dropped data is relatively simple. To receive dragged text, the sample DropHelper
is implemented in DropTargetFragment.onViewCreated
:
DropHelper.configureView( requireActivity(), targetText, arrayOf( ClipDescription.MIMETYPE_TEXT_PLAIN, MIME_TYPE_EXTERNAL ), DropHelper.Options.Builder() .setHighlightColor(requireContext().getColor(R.color.gray)) .setHighlightCornerRadiusPx(0) .build() ) { _, payload -> val item = payload.clip.getItemAt(0) val remaining = payload.partition { it == item }.second if (payload.clip.description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { targetTextView.text = item.text // could also be a URI, see the full code in the sample } remaining }
The parameters of DropHelper.configureView
perform the following:
- Attaches to the Activity
- Attaches to the TextView where items can be dropped
- Sets the filter of allowable content types
- Defines the drop visual indicator properties
The method body is executed when the drop is completed, and shows how to extract text and assign it to the target text view (you could also take other steps here if required).
Supporting text drag and drop requires just a few lines of code and is a great first step towards a better multi-tasking user experience.
Drag and drop an image
Here’s the sample app side-by-side with our TwoNote sample, which demonstrates dragging an image across applications while multi-tasking.
The DragStartHelper
implementation in DragSourceFragment.onViewCreated
looks similar to the draggable text version above, but unlike text data, binary data like images is not included in the drag and drop message, instead a URI is created which is used by the application where the drop occurs to retrieve the image data. Here’s a summarized version of the code:
DragStartHelper(binding.dragImageView) { view, _ -> /* code removed from blog for clarity: * - Ensure file exists provider location * - Get URI from FileProvider */ val dragClipData = ClipData.newUri(requireContext().contentResolver, DRAG_FOLDER_NAME, imageUri) val dragShadow = View.DragShadowBuilder(view) view.startDragAndDrop( dragClipData, dragShadow, null, // Need to Use the DRAG_FLAG_GLOBAL_URI_READ to allow other apps to read from our // content provider. Without it, other apps won't receive the drag events. DRAG_FLAG_GLOBAL.or(View.DRAG_FLAG_GLOBAL_URI_READ) ) }.attach() }
The FileProvider
has been set up using the <provider>
element in the AndroidManifest.xml, with the file provider path defined in XML matching the path that’s used in the fragment to ensure the image file exists in filesystem. When the image is dropped, a content resolver is used to retrieve the image data stream from the URI and process it (save to disk or display on screen).
Going beyond simple text to images, formatted text, or other complex data types adds polish to your multi-tasking user experience for apps on dual-screen, foldable, and large screens like tablets or desktops.
Resources and feedback
Please share your experiences with creating foldable and large screen user experiences for multi-tasking – reach out via the feedback forum or on Twitter @surfaceduodev.
Our last Twitch livestream for the year will be on Friday, 16th December at 11am PST to chat about drag and drop, and anything else you’re curious to learn. We’ll be taking a break from streaming over the holidays – see you online again in 2023!
work becomes easy