codete Android Nougat Multi window and Drag n Drop Tutorial 1 main 6f74e5eb9c
Codete Blog

Android Nougat: Multi-window and Drag’n’Drop Tutorial

avatar male f667854eaa

09/11/2016 |

9 min read

Hubert Kosacki

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 to 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:

<script src="https://gist.github.com/hkosacki/aaa48d23832db6be7fe9ee0adeb2bf2f.js"></script>

ClipDescription description = new ClipDescription(null, new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN});
ClipData.Item clipDataItem = new ClipData.Item("myItem");
ClipData draggedData = new ClipData(description, clipDataItem);
View.DragShadowBuilder dragShadowBuilder = new View.DragShadowBuilder(view);
// start drag'n'drop operation
view.startDragAndDrop(draggedData, dragShadowBuilder, null, 0);

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 ClipDescription object, 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 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 setOnLongClickListener() method.

Receiving a 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:

<script src="https://gist.github.com/hkosacki/2f8ec0b1a52011b0a80513de2dba0b81.js"></script>

imageTrash.setOnDragListener(new View.OnDragListener() {
   @Override
   public boolean onDrag(View view, DragEvent dragEvent) {
       switch(dragEvent.getAction()){
           case DragEvent.ACTION_DRAG_STARTED:
           case DragEvent.ACTION_DRAG_ENTERED:
           case DragEvent.ACTION_DRAG_LOCATION:
           case DragEvent.ACTION_DRAG_EXITED:
           case DragEvent.ACTION_DRAG_ENDED:
              return true;
           case DragEvent.ACTION_DROP:
              Log.d("drop!",dragEvent.getClipData().getItemAt(0).getText().toString());
              return true;
       }
       return false;
   }
});

All these simple examples can be found under the following link, within this particular commit:

https://github.com/hkosacki/discover-android-n/commit/a0dcf15bff1485d3e22592aebadb492fb7b62402

 

Android Multi-window

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 at how it looks on an example of an own file explorer application with enabling drag and drop for files.

Brace yourselves!

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.

build.gradle modifications

First of all, in order to assure full compatibility, please make sure you are targeting the 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.

Fig. 1. When splitting screen while application not targeting API 24+ is running, a warning is being displayed

AndroidManifest.xml modifications

All you need to do in your AndroidManifest.xml file is just to add a 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 being resized (no surprise here).

Fig 2. If you set the android:resizeableActivity property to false, the activity won’t allow splitting the screen

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 the needed knowledge of using these features 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 the Gmail app works really cool with it, so why not give it a try?

Prerequisites

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:

  1. Display sorted list of files by type (directories first, then files) and names in a RecyclerView.
  2. Start the file manager from the external SD Card location and give the possibility to navigate between directories.
  3. Drag the files outside of the app (not directories).
  4. 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.

Animated transitions

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 the 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:

https://github.com/hkosacki/discover-android-n/commit/94f85f9a7da571c436fff77ea91855518729ea94

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 behavior 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 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 at the beginning of this article. I have added only several modifications that reflect the actual kind of data that is attached to the DragEvent (the file and its MIME type) and the flags for giving proper attributes to this event. They are:

  • DRAG_FLAG_GLOBAL – this makes the DragEvent able 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 DragEvent to get the read access to the content URI that was added to ClipData object (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!

<script src="https://gist.github.com/hkosacki/c48eb976386e6db0d7999fc684904068.js"></script>
/*
* Used to register to each adapter item, to handle long click events
*/
private OnItemLongClickListener longClickListener = new OnItemLongClickListener<File>() {
   @Override
   public void onItemLongClick(View view, File f) {
       if (f.isDirectory()) {
           Snackbar.make(view, "Sorry, no drag'n'drop support for directories", Snackbar.LENGTH_SHORT).show();
           return;
       }
       // prepare drag parameters
       ClipDescription description = new ClipDescription(f.getName(), new String[]{ClipDescription.MIMETYPE_TEXT_URILIST});
       ClipData.Item clipDataItem = new ClipData.Item(Uri.fromFile(f));
       ClipData draggedData = new ClipData(description, clipDataItem);
       View.DragShadowBuilder dragShadowBuilder = new View.DragShadowBuilder(view);
       // start drag and drop operation for proper platform
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
           view.startDragAndDrop(draggedData, dragShadowBuilder, null, View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ);
       } else {
           //noinspection deprecation
           view.startDrag(draggedData, dragShadowBuilder, null, 0);
       }
   }
};

Fig. 4. Dragging file operation: (a) a file is being dragged over the drop files area in the Gmail app, (b) the file has been added, (c) an attempt to drag and drop a directory ends in failure

 

Summary

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.

 

Sources:

Rated: 5.0 / 1 opinions
avatar male f667854eaa

Hubert Kosacki

Android fanatic. Problem solver. Mobile technologies enthusiast.

Our mission is to accelerate your growth through technology

Contact us

Codete Global
Spółka z ograniczoną odpowiedzialnością

Na Zjeździe 11
30-527 Kraków

NIP (VAT-ID): PL6762460401
REGON: 122745429
KRS: 0000983688

Get in Touch
  • icon facebook
  • icon linkedin
  • icon instagram
  • icon youtube
Offices
  • Kraków

    Na Zjeździe 11
    30-527 Kraków
    Poland

  • Lublin

    Wojciechowska 7E
    20-704 Lublin
    Poland

  • Berlin

    Bouchéstraße 12
    12435 Berlin
    Germany