summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Vadim Tryshev <vadimt@google.com> 2016-09-15 16:19:15 -0700
committer Vadim Tryshev <vadimt@google.com> 2016-09-16 11:20:16 -0700
commit1edc6daf1d13ec472da2aae2a2a88d130c495cb7 (patch)
tree50712e5b15e1a405fcb0db6674c6b2318473bf5c
parentb2e0ed87722ff975cb2974d8ab25f17a4f2ae4b3 (diff)
Eliminating the “black hole” effect with location/drop events.
Currently, a container view that doesn’t accept events, but has a child that accepts events, prevents its parent from receiving LOCATION/DROP events while the drag is over the container (but not the child). This is a bug. With this fix, such a container will prevent the parent from invoking a (second) LOCATION/DROP event only if the event was really delivered to any of its descendants. To know whether the event was delivered, I added DragEvent.mEventHandlerWasCalled member. EXITED/ENTERED events are now generated upon delivery of the event that has coordinates in it. Current view that has drag focus is now global to reflect the fact that it’s one per process. Bug: 31469490 Change-Id: I248e8d1de87b7734853136eb4719f7571cea91d5
-rw-r--r--core/java/android/view/DragEvent.java2
-rw-r--r--core/java/android/view/View.java12
-rw-r--r--core/java/android/view/ViewGroup.java113
-rw-r--r--core/java/android/view/ViewRootImpl.java41
4 files changed, 60 insertions, 108 deletions
diff --git a/core/java/android/view/DragEvent.java b/core/java/android/view/DragEvent.java
index b0f15b5f2329..e0a6fac1891f 100644
--- a/core/java/android/view/DragEvent.java
+++ b/core/java/android/view/DragEvent.java
@@ -134,6 +134,7 @@ public class DragEvent implements Parcelable {
Object mLocalState;
boolean mDragResult;
+ boolean mEventHandlerWasCalled;
private DragEvent mNext;
private RuntimeException mRecycledLocation;
@@ -435,6 +436,7 @@ public class DragEvent implements Parcelable {
mClipData = null;
mClipDescription = null;
mLocalState = null;
+ mEventHandlerWasCalled = false;
synchronized (gRecyclerLock) {
if (gRecyclerUsed < MAX_RECYCLED) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 908658feb29c..1d972eafa379 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -40,7 +40,6 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Interpolator;
import android.graphics.LinearGradient;
@@ -20874,6 +20873,17 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* </p>
*/
public boolean dispatchDragEvent(DragEvent event) {
+ event.mEventHandlerWasCalled = true;
+ if (event.mAction == DragEvent.ACTION_DRAG_LOCATION ||
+ event.mAction == DragEvent.ACTION_DROP) {
+ // About to deliver an event with coordinates to this view. Notify that now this view
+ // has drag focus. This will send exit/enter events as needed.
+ getViewRootImpl().setDragFocus(this, event);
+ }
+ return callDragEventHandler(event);
+ }
+
+ final boolean callDragEventHandler(DragEvent event) {
ListenerInfo li = mListenerInfo;
//noinspection SimplifiableIfStatement
if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6933efc15b68..987db0ac58ed 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -153,9 +153,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
*/
Transformation mInvalidationTransformation;
- // View currently under an ongoing drag. Can be null, a child or this window.
- private View mCurrentDragView;
-
// Metadata about the ongoing drag
private DragEvent mCurrentDragStartEvent;
private boolean mIsInterestedInDrag;
@@ -1362,16 +1359,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final float tx = event.mX;
final float ty = event.mY;
- ViewRootImpl root = getViewRootImpl();
-
// Dispatch down the view hierarchy
final PointF localPoint = getLocalPoint();
switch (event.mAction) {
case DragEvent.ACTION_DRAG_STARTED: {
- // clear state to recalculate which views we drag over
- mCurrentDragView = null;
-
// Set up our tracking of drag-started notifications
mCurrentDragStartEvent = DragEvent.obtain(event);
if (mChildrenInterestedInDrag == null) {
@@ -1434,60 +1426,15 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
}
} break;
- case DragEvent.ACTION_DRAG_LOCATION: {
+ case DragEvent.ACTION_DRAG_LOCATION:
+ case DragEvent.ACTION_DROP: {
// Find the [possibly new] drag target
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
- // notifications to the framework] and tell the new target that the drag
- // has entered its bounds. The root will see setDragFocus() calls all
- // the way down to the final leaf view that is handling the LOCATION event
- // before reporting the new potential recipient to the framework.
- if (mCurrentDragView != target) {
- root.setDragFocus(target);
-
- final int action = event.mAction;
- // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
- event.mX = 0;
- event.mY = 0;
-
- // 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;
- 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, otherwise
- // send it to this window.
- if (target != null) {
- event.mAction = DragEvent.ACTION_DRAG_ENTERED;
- 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
- event.mX = tx;
- event.mY = ty;
- }
-
- // Dispatch the actual drag location notice, localized into its coordinates
+ // Dispatch the actual drag notice, localized into the target coordinates.
if (target != null) {
if (target != this) {
event.mX = localPoint.x;
@@ -1497,55 +1444,14 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
event.mX = tx;
event.mY = ty;
- } else {
- retval = super.dispatchDragEvent(event);
- }
- }
- } break;
- /* Entered / exited dispatch
- *
- * DRAG_ENTERED is not dispatched downwards from ViewGroup. The reason for this is
- * 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
- * is also no longer under the drag point.
- */
-
- case DragEvent.ACTION_DRAG_EXITED: {
- if (mCurrentDragView != null) {
- final View view = mCurrentDragView;
- if (view != this) {
- view.dispatchDragEvent(event);
- view.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
- view.refreshDrawableState();
+ if (!event.mEventHandlerWasCalled && mIsInterestedInDrag) {
+ // The child didn't invoke any event handler, but this view is interested in
+ // drag, so the event goes to this view.
+ retval = super.dispatchDragEvent(event);
+ }
} else {
- super.dispatchDragEvent(event);
- }
-
- mCurrentDragView = null;
- }
- } break;
-
- case DragEvent.ACTION_DROP: {
- if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, "Drop event: " + event);
- View target = findFrontmostDroppableChildAt(event.mX, event.mY, localPoint);
- if (target != null) {
- if (ViewDebug.DEBUG_DRAG) Log.d(View.VIEW_LOG_TAG, " dispatch drop to " + target);
- event.mX = localPoint.x;
- event.mY = localPoint.y;
- 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");
+ retval = super.dispatchDragEvent(event);
}
}
} break;
@@ -1592,6 +1498,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
final boolean canAccept = child.dispatchDragEvent(mCurrentDragStartEvent);
mCurrentDragStartEvent.mX = tx;
mCurrentDragStartEvent.mY = ty;
+ mCurrentDragStartEvent.mEventHandlerWasCalled = false;
if (canAccept) {
mChildrenInterestedInDrag.add(child);
if (!child.canAcceptDrag()) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 1d541f6c0c4c..8abaa14810e1 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -5523,9 +5523,8 @@ public final class ViewRootImpl implements ViewParent,
if (what == DragEvent.ACTION_DRAG_EXITED) {
// A direct EXITED event means that the window manager knows we've just crossed
// a window boundary, so the current drag target within this one must have
- // just been exited. Send it the usual notifications and then we're done
- // for now.
- mView.dispatchDragEvent(event);
+ // just been exited. Send the EXITED notification to the current drag view, if any.
+ setDragFocus(null, event);
} else {
// For events with a [screen] location, translate into window coordinates
if ((what == DragEvent.ACTION_DRAG_LOCATION) || (what == DragEvent.ACTION_DROP)) {
@@ -5548,6 +5547,12 @@ public final class ViewRootImpl implements ViewParent,
// Now dispatch the drag/drop event
boolean result = mView.dispatchDragEvent(event);
+ if (what == DragEvent.ACTION_DRAG_LOCATION && !event.mEventHandlerWasCalled) {
+ // If the LOCATION event wasn't delivered to any handler, no view now has a drag
+ // focus.
+ setDragFocus(null, event);
+ }
+
// If we changed apparent drag target, tell the OS about it
if (prevDragView != mCurrentDragView) {
try {
@@ -5575,6 +5580,7 @@ public final class ViewRootImpl implements ViewParent,
// When the drag operation ends, reset drag-related state
if (what == DragEvent.ACTION_DRAG_ENDED) {
+ mCurrentDragView = null;
setLocalDragState(null);
mAttachInfo.mDragToken = null;
if (mAttachInfo.mDragSurface != null) {
@@ -5634,9 +5640,36 @@ public final class ViewRootImpl implements ViewParent,
return mLastTouchSource;
}
- public void setDragFocus(View newDragTarget) {
+ public void setDragFocus(View newDragTarget, DragEvent event) {
if (mCurrentDragView != newDragTarget) {
+ // Send EXITED and ENTERED notifications to the old and new drag focus views.
+
+ final float tx = event.mX;
+ final float ty = event.mY;
+ final int action = event.mAction;
+ // Position should not be available for ACTION_DRAG_ENTERED and ACTION_DRAG_EXITED.
+ event.mX = 0;
+ event.mY = 0;
+
+ if (mCurrentDragView != null) {
+ event.mAction = DragEvent.ACTION_DRAG_EXITED;
+ mCurrentDragView.callDragEventHandler(event);
+ mCurrentDragView.mPrivateFlags2 &= ~View.PFLAG2_DRAG_HOVERED;
+ mCurrentDragView.refreshDrawableState();
+ }
+
mCurrentDragView = newDragTarget;
+
+ if (newDragTarget != null) {
+ event.mAction = DragEvent.ACTION_DRAG_ENTERED;
+ newDragTarget.callDragEventHandler(event);
+ newDragTarget.mPrivateFlags2 |= View.PFLAG2_DRAG_HOVERED;
+ newDragTarget.refreshDrawableState();
+ }
+
+ event.mAction = action;
+ event.mX = tx;
+ event.mY = ty;
}
}