Eliminating the black hole effect with location/drop events. am: 1edc6daf1d

am: 32394cee01

Change-Id: Icbcf0cb248bc823f72fb61373732a08ee56c51cc
This commit is contained in:
Vadim Tryshev
2016-09-20 07:20:07 +00:00
committed by android-build-merger
4 changed files with 60 additions and 108 deletions

View File

@@ -134,6 +134,7 @@ public class DragEvent implements Parcelable {
Object mLocalState;
boolean mDragResult;
boolean mEventHandlerWasCalled;
private DragEvent mNext;
private RuntimeException mRecycledLocation;
@@ -439,6 +440,7 @@ public class DragEvent implements Parcelable {
mClipData = null;
mClipDescription = null;
mLocalState = null;
mEventHandlerWasCalled = false;
synchronized (gRecyclerLock) {
if (gRecyclerUsed < MAX_RECYCLED) {

View File

@@ -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;
@@ -20876,6 +20875,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

View File

@@ -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,58 +1444,17 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
event.mX = tx;
event.mY = ty;
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 {
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();
} 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");
}
}
} break;
}
return retval;
@@ -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()) {

View File

@@ -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;
}
}