Fixing non-delivering of DRAG_START event to each view
The problem was that GroupView.dispatchDragEvent didn’t send ACTION_DRAG_STARTED
to GroupView itself in case when there is a child that is interested
in this drag.
Now, delivering DRAG_STARTED to all children and to GroupView itself.
Also corrected delivery of DRAG_ENDED to match docs: it will be delivered
only to the views that returned true from DRAG_STARTED.
Same story with Enter, Exit and Location notifications: they are delivered
only to children that returned true from DRAG_START.
Delivery to
the ViewGroup itself never depends on a child’s return value.
Bug: 22028725
Change-Id: I44495d6da8423c9cc012f029233f4604cb60c74b
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6dca26b..a6639d5 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -151,15 +151,13 @@
*/
Transformation mInvalidationTransformation;
- // View currently under an ongoing drag
+ // View currently under an ongoing drag. Can be null, a child or this window.
private View mCurrentDragView;
// Metadata about the ongoing drag
- private DragEvent mCurrentDrag;
- private HashSet<View> mDragNotifiedChildren;
-
- // Does this group have a child that can accept the current drag payload?
- private boolean mChildAcceptsDrag;
+ private DragEvent mCurrentDragStartEvent;
+ private boolean mIsInterestedInDrag;
+ private HashSet<View> mChildrenInterestedInDrag;
// Used during drag dispatch
private PointF mLocalPoint;
@@ -1276,9 +1274,9 @@
}
// in all cases, for drags
- if (mCurrentDrag != null) {
- if (newVisibility == VISIBLE) {
- notifyChildOfDrag(child);
+ if (newVisibility == VISIBLE && mCurrentDragStartEvent != null) {
+ if (!mChildrenInterestedInDrag.contains(child)) {
+ notifyChildOfDragStart(child);
}
}
}
@@ -1386,61 +1384,66 @@
mCurrentDragView = null;
// Set up our tracking of drag-started notifications
- mCurrentDrag = DragEvent.obtain(event);
- if (mDragNotifiedChildren == null) {
- mDragNotifiedChildren = new HashSet<View>();
+ mCurrentDragStartEvent = DragEvent.obtain(event);
+ if (mChildrenInterestedInDrag == null) {
+ mChildrenInterestedInDrag = new HashSet<View>();
} else {
- mDragNotifiedChildren.clear();
+ mChildrenInterestedInDrag.clear();
}
// Now dispatch down to our children, caching the responses
- mChildAcceptsDrag = false;
final int count = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < count; i++) {
final View child = children[i];
child.mPrivateFlags2 &= ~View.DRAG_MASK;
if (child.getVisibility() == VISIBLE) {
- final boolean handled = notifyChildOfDrag(children[i]);
- if (handled) {
- mChildAcceptsDrag = true;
+ if (notifyChildOfDragStart(children[i])) {
+ retval = true;
}
}
}
- // Return HANDLED if one of our children can accept the drag
- if (mChildAcceptsDrag) {
+ // Notify itself of the drag start.
+ mIsInterestedInDrag = super.dispatchDragEvent(event);
+ if (mIsInterestedInDrag) {
retval = true;
}
} break;
case DragEvent.ACTION_DRAG_ENDED: {
// Release the bookkeeping now that the drag lifecycle has ended
- if (mDragNotifiedChildren != null) {
- for (View child : mDragNotifiedChildren) {
- // If a child was notified about an ongoing drag, it's told that it's over
- child.dispatchDragEvent(event);
+ if (mChildrenInterestedInDrag != null) {
+ for (View child : mChildrenInterestedInDrag) {
+ // If a child was interested in the ongoing drag, it's told that it's over
+ if (child.dispatchDragEvent(event)) {
+ retval = true;
+ }
child.mPrivateFlags2 &= ~View.DRAG_MASK;
child.refreshDrawableState();
}
- mDragNotifiedChildren.clear();
- if (mCurrentDrag != null) {
- mCurrentDrag.recycle();
- mCurrentDrag = null;
+ mChildrenInterestedInDrag.clear();
+ if (mCurrentDragStartEvent != null) {
+ mCurrentDragStartEvent.recycle();
+ mCurrentDragStartEvent = null;
}
}
- // We consider drag-ended to have been handled if one of our children
- // had offered to handle the drag.
- if (mChildAcceptsDrag) {
- retval = true;
+ if (mIsInterestedInDrag) {
+ if (super.dispatchDragEvent(event)) {
+ retval = true;
+ }
+ mIsInterestedInDrag = false;
}
} break;
case DragEvent.ACTION_DRAG_LOCATION: {
// Find the [possibly new] drag target
- final View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint);
+ View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint);
+ if (target == null && mIsInterestedInDrag) {
+ target = this;
+ }
// If we've changed apparent drag target, tell the view root which view
// we're over now [for purposes of the eventual drag-recipient-changed
@@ -1452,35 +1455,49 @@
root.setDragFocus(target);
final int action = event.mAction;
- // If we've dragged off of a child view, send it the EXITED message
+ // If we've dragged off of a child view or this window, send it the EXITED message
if (mCurrentDragView != null) {
final View view = mCurrentDragView;
event.mAction = DragEvent.ACTION_DRAG_EXITED;
- view.dispatchDragEvent(event);
- view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
- view.refreshDrawableState();
+ if (view != this) {
+ view.dispatchDragEvent(event);
+ view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
+ view.refreshDrawableState();
+ } else {
+ super.dispatchDragEvent(event);
+ }
}
+
mCurrentDragView = target;
- // If we've dragged over a new child view, send it the ENTERED message
+ // If we've dragged over a new child view, send it the ENTERED message, otherwise
+ // send it to this window.
if (target != null) {
event.mAction = DragEvent.ACTION_DRAG_ENTERED;
- target.dispatchDragEvent(event);
- target.mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
- target.refreshDrawableState();
+ if (target != this) {
+ target.dispatchDragEvent(event);
+ target.mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
+ target.refreshDrawableState();
+ } else {
+ super.dispatchDragEvent(event);
+ }
}
event.mAction = action; // restore the event's original state
}
// Dispatch the actual drag location notice, localized into its coordinates
if (target != null) {
- event.mX = localPoint.x;
- event.mY = localPoint.y;
+ if (target != this) {
+ event.mX = localPoint.x;
+ event.mY = localPoint.y;
- retval = target.dispatchDragEvent(event);
+ retval = target.dispatchDragEvent(event);
- event.mX = tx;
- event.mY = ty;
+ event.mX = tx;
+ event.mY = ty;
+ } else {
+ retval = super.dispatchDragEvent(event);
+ }
}
} break;
@@ -1490,6 +1507,7 @@
* that we're about to get the corresponding LOCATION event, which we will use to
* determine which of our children is the new target; at that point we will
* push a DRAG_ENTERED down to the new target child [which may itself be a ViewGroup].
+ * If no suitable child is detected, dispatch to this window.
*
* DRAG_EXITED *is* dispatched all the way down immediately: once we know the
* drag has left this ViewGroup, we know by definition that every contained subview
@@ -1499,9 +1517,13 @@
case DragEvent.ACTION_DRAG_EXITED: {
if (mCurrentDragView != null) {
final View view = mCurrentDragView;
- view.dispatchDragEvent(event);
- view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
- view.refreshDrawableState();
+ if (view != this) {
+ view.dispatchDragEvent(event);
+ view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
+ view.refreshDrawableState();
+ } else {
+ super.dispatchDragEvent(event);
+ }
mCurrentDragView = null;
}
@@ -1517,6 +1539,8 @@
retval = target.dispatchDragEvent(event);
event.mX = tx;
event.mY = ty;
+ } else if (mIsInterestedInDrag) {
+ retval = super.dispatchDragEvent(event);
} else {
if (ViewDebug.DEBUG_DRAG) {
Log.d(View.VIEW_LOG_TAG, " not dropped on an accepting view");
@@ -1525,11 +1549,6 @@
} break;
}
- // If none of our children could handle the event, try here
- if (!retval) {
- // Call up to the View implementation that dispatches to installed listeners
- retval = super.dispatchDragEvent(event);
- }
return retval;
}
@@ -1551,16 +1570,17 @@
return null;
}
- boolean notifyChildOfDrag(View child) {
+ boolean notifyChildOfDragStart(View child) {
+ // The caller guarantees that the child is not in mChildrenInterestedInDrag yet.
+
if (ViewDebug.DEBUG_DRAG) {
Log.d(View.VIEW_LOG_TAG, "Sending drag-started to view: " + child);
}
- boolean canAccept = false;
- if (! mDragNotifiedChildren.contains(child)) {
- mDragNotifiedChildren.add(child);
- canAccept = child.dispatchDragEvent(mCurrentDrag);
- if (canAccept && !child.canAcceptDrag()) {
+ final boolean canAccept = child.dispatchDragEvent(mCurrentDragStartEvent);
+ if (canAccept) {
+ mChildrenInterestedInDrag.add(child);
+ if (!child.canAcceptDrag()) {
child.mPrivateFlags2 |= View.PFLAG2_DRAG_CAN_ACCEPT;
child.refreshDrawableState();
}
@@ -3005,10 +3025,11 @@
mLayoutCalledWhileSuppressed = false;
// Tear down our drag tracking
- mDragNotifiedChildren = null;
- if (mCurrentDrag != null) {
- mCurrentDrag.recycle();
- mCurrentDrag = null;
+ mChildrenInterestedInDrag = null;
+ mIsInterestedInDrag = false;
+ if (mCurrentDragStartEvent != null) {
+ mCurrentDragStartEvent.recycle();
+ mCurrentDragStartEvent = null;
}
final int count = mChildrenCount;