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:
…on Android 7.0, we get this, from the same code:
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.