The newest Android release – version 7.0 (Nougat) – has introduced the possibility to enable drag and drop feature across the apps. This article will cover the case of applying the drag’n’drop feature in an app and using it across them, based on an example of the file explorer app.
Drag and drop Android
The possibility of ordering items on a list is a typical use case in Android applications (see eg. music playlists, language priority list in Settings etc.). Everything gets done within one screen, one activity, maybe across several fragments. But what about handling it outside the app? Let’s face it!
Building a simple drag’n’drop event with data
Those, who have already played with dragging the UI items, know this is not as difficult as it might seem. It’s all about invoking one method on the view, that has been long-pressed and prior preparing proper parameters. Let’s consider the following case: I want to bind some plain text to the image that has the possibility to be dragged. This is being done by the following piece of code:
The wanted data is being associated with the drag event using the
ClipData class. It consists of two items:
- Data description: it’s located within
ClipDescriptionobject, it keeps the information about the label of dragged data (not required, may be null) and an array of MIME types that are related to the held data.
- Clipped data itself. Usually represented by a URI identifier or a String, like in this case.
When dragging, in order to have a clear representation of an item being dragged, it’s good to provide a good visual feedback.
This can be easily achieved by creating an instance of
DragShadowBuilder class passing as a parameter the view that should be reflected (in this particular case – it will be an image).
We have 2 more parameters left now:
null– so-called “local state object”. Using this parameter you are able to attach some data to the drag event receiver about the operation being done. You can pass there actually anything, as the parameter type is Object.
0– this is a placeholder for flags, that haven’t been used with Android versions prior to Nougat. Not used within the same activity.
When all needed parameters are collected, you can safely proceed with starting the drag event using View’s
DragEvent is also an easy part. All you need is just to implement an
OnDragListener() object and set it to a view, that should be drop-enabled, and consume the dragged data in there. Just as follows:
All these simple examples can be found under the following link, within this particular commit:
Running two activities one by one on the same screen is a nice feature, that can be used not only for consuming multiple contents at the same time, but may also improve your performance. Let’s have a look how it looks on an example of an own file explorer application with enabling drag and drop for files.
In order to make your app compatible with a split-screen, you need to be aware of some pre-requisite configurations that should be applied to your app.
First of all, in order to assure full compatibility, please make sure you are targeting minimum Nougat 7.0 API (24). If targeting lower, your app should be able to split the screen anyway, but with a warning message being displayed in a Toast.
All you need to do in your AndroidManifest.xml file is just to add proper property in the activity declaration. You need to add the
android:resizeableActivity attribute and set its value to
true – then you will be able to use only part of the screen. If you set this value to
false, then the Activity won’t allow to be resized (no surprise here).
That’s basically all you need to know when making your app able to split the screen (multi-panel screen). It’s pretty easy, ain’t it?
File explorer with drag & drop
After all, when we have all needed knowledge of using these feature within an app, we can combine these two features in order to build a supersophisticated, drag’n’drop enabled, cross-application working File manager application.
It turns out, that Gmail app works really cool with it, so why not to give it a try?
To create the app, I decided to use the following libraries:
- Butterknife – who likes manual view finding and casting?
- Eventbus – to make the communication between an adapter and an activity easier,
- Guava – to help with sorting file lists.
The idea for the app is simple – basic requirements are:
- Display sorted list of files by type (directories first, then files) and names in a
- Start the file manager from external SD Card location and give the possibility to navigate between directories.
- Drag the files outside of the app (not directories).
- Apply eye-candy animations, where possible.
The implementation of the whole file listing mechanisms won’t be discussed, I’ll focus on the tricky part only.
While the communication between the adapter displaying list items and reacting to the user’s actions has been implemented using EventBus (broadcasting an event of entering a new directory), the main challenge for me was to use animations in a way that would be elegant (from the source code perspective) and eye-catching from the UI. I decided to use the
?android:attr/selectableItemBackground drawable as the list item background, so that when the user touches a list item, a nice ripple effect will be drawn.
However, although it looked very well when the file item has been selected – in case of pressing a directory, the entering new path event was broadcasted too soon, so the ripple effect wasn’t even visible. Using good, old friend
postDelayed() may be sometimes an option (but not today!), so it was a good opportunity to try to apply some view animations. What I have done, was to prepare a nice fade out animation that lasts long enough to see the ripple effect. Then, right after it ends, as a callback, the rest of the app logic is being processed.
Alright, after the list has been cleared, it needs to be populated now. What about showing the items one by one, making them appear rising from bottom to top and fading in? For all of these, please have a look at the commit below:
Clicking, dragging and dropping file items
All of the logic for interacting with list items is being done within
RecyclerView.Adapter class implementation. The actions that need to be supported are:
- Click event – to invoke the Open file action,
- Long click event – for starting the drag event.
Clicking file item and opening it
Sharing the intent to open the file is not that straightforward as it has been in previous Android releases. Starting with Android Nougat, when trying to pass a file URI to another application, a
FileUriExposedException will be thrown. This behaviour has been changed as a result of security policy improvement, that restricts the access to shared contents to the app that has shared it, not using the access of the the target one. In order, to do this properly, Google forces you to use the FileProvider. How to use it? I really recommend this article – it has enlightened me a lot.
Long click on an item
There is no rocket science in here. All the information needed has been covered in the beginning of this article. I have added only several modifications that reflect actual kind of data that is attached to the
DragEvent (the file and it’s MIME type) and the flags for giving proper attributes to this event. They are:
DRAG_FLAG_GLOBAL– this makes the
DragEventable to be made outside the application,
DRAG_FLAG_GLOBAL_URI_READ– this flag, when the previous one is also set, will allow the application that receives the
DragEventto get the read access to the content URI that was added to
ClipDataobject (when I was missing this, the Gmail app has shown a meaningful log and a Toast with the following message: Failed to obtain read permissions for dragged items). This may be useful if the target app should have access to these contents.
Friendly Tip: ALWAYS use the
>= operator when coding-around API-level specific features. Wondering why? Please see this: https://github.com/hkosacki/discover-android-n/commit/f89e0916cca51b57fac0ca53923c96e83c929f17. In the meantime, after upgrading to Nougat 7.1.1 preview, the drag’n’drop feature in my app just stopped working. I was really worried, that something changed within API level 25 (fortunately, it didn’t).
Voilà! That’s it, we have implemented the drag & drop feature in an app!
That’s the way I see it. If you think there might be some better approaches, I’m open to discussion. Feel free to reach me in the comments or directly on GH submitting a pull request. You can find complete sources here: https://github.com/hkosacki/discover-android-n/tree/master/drag-n-drop.