PSA: Drag-and-Drop Behavior Change in Android 7.0

Dan Lew has run across a couple of cases where the behavior of Android’s native drag-and-drop APIs differ between Android 7.0 and earlier versions of Android. If you are using startDrag() (or the newer startDragAndDrop()), View.OnDragListener, and the like, these behavior changes may be important for you. I’ll explain the problem as best I can, and I will try to remember to update this post if Dan posts more about it, beyond the issues that he filed in the Android issue tracker.

Both issues pertain to nested drop targets, where you are listening for drag events both on some container and on some view inside of that container. For example, if you are using the drag-and-drop APIs to support reordering items in a LinearLayout or RecyclerView, there is a good chance that you will need to have listeners both on that container and on existing items in the container (e.g., to animate them out of the way to allow the user to drop in the newly-vacant spot).

The overarching problem, most simply illustrated in Dan’s first issue, is that drag events are inclusive on Android 6.0 and earlier and exclusive on Android 7.0.

Suppose that we have the following portion of a layout:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="@+id/outer_container"
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:background="@color/outer_normal"
  android:padding="48dp">

  <FrameLayout
    android:id="@+id/inner_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/inner_normal"
    android:padding="48dp">

    <ImageView
      android:id="@+id/thumbnail_large"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:background="@color/image_normal"
      android:contentDescription="@string/icon"
      android:scaleType="centerInside" />
  </FrameLayout>

</FrameLayout>

For various reasons, we have hooked up event listeners for all three things in the layout: both FrameLayout containers and the ImageView. These are nested, with one FrameLayout holding the other FrameLayout, which holds the ImageView.

On Android 6.0 and earlier, drag events are inclusive. In other words, if the user has dragged an item into the inner FrameLayout, this is also considered to be inside the outer FrameLayout. From an event standpoint, the outer FrameLayout only gets an ACTION_DRAG_EXITED event when the dragged item leaves its outer boundaries.

However, on Android 7.0, drag events are exclusive. If the user drags an item into the inner FrameLayout, the item will exit the outer FrameLayout from a drag-and-drop perspective.

So, whereas on Android 6.0, we get this:

Android 6.0 Drag-and-Drop Behavior

…on Android 7.0, we get this, from the same code:

Android 7.0 Drag-and-Drop Behavior

So, you get different events based on Android version. Which one is “right” is somewhat immaterial, in that existing code will not behave the same, which is why these sorts of undocumented behavior changes are unfortunate.

Dan’s second issue is along the same lines: a parent container stops receiving events when the dragged item is over a child. This particular issue is a bit more complex, involving one of the containers registering for drag events but returning false on ACTION_DRAG_STARTED (indicating that it is not interested in further events for this drag operation). It also misses a glorious opportunity to open an issue report with “Two ViewGroups and an EditText walk into a bar”.

If you are using the drag-and-drop APIs, be sure to test your app thoroughly on Android 7.0, as clearly some changes were made to the implementation. If you use nested drop targets, as in these issues, you really need to test your app thoroughly, so you can adjust your behavior. In the sample app that I wrote to illustrate the problem, I also have a DropTarget helper class that helps get Android 7.0 to behave more like Android 6.0, with inclusive drag events, though that code has not been tested beyond the sample app.

If you happen to be coming to droidcon NYC, you can thank Dan in person for his bug sleuthing, as part of attending his talk on efficient Android layouts. I’ll be there as well, talking about drag-and-drop, including covering this behavior change.