Fix touch interceptions.

We were unfortunately passing through all touches and never intercepting
events from the message ScrollView. This was desirable when the drag was
over the message content, but in the surrounding chrome it would lead to
pressed states while dragging, sometimes accidentally causing text
selection. This change makes that logic special cased to WebViews only.

Bug: 5361173
Change-Id: Icf535c015cec4a79a5ad7eba3d6c5aa7bd572a8a
This commit is contained in:
Ben Komalo 2011-10-14 18:39:52 -07:00
parent 1f6769facc
commit 98c968d178
1 changed files with 71 additions and 6 deletions

View File

@ -18,10 +18,16 @@
package com.android.email.view;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ScrollView;
import java.util.ArrayList;
/**
* A {@link ScrollView} that will never lock scrolling in a particular direction.
*
@ -44,31 +50,90 @@ public class NonLockingScrollView extends ScrollView {
}
/**
* Whether or not this view is in the middle of a drag.
* Whether or not the contents of this view is being dragged by one of the children in
* {@link #mChildrenNeedingAllTouches}.
*/
private boolean mInDrag = false;
private boolean mInCustomDrag = false;
/**
* The list of children who should always receive touch events, and not have them intercepted.
*/
private final ArrayList<View> mChildrenNeedingAllTouches = new ArrayList<View>();
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
final int action = ev.getActionMasked();
final boolean isUp = action == MotionEvent.ACTION_UP;
if (isUp && mInDrag) {
if (isUp && mInCustomDrag) {
// An up event after a drag should be intercepted so that child views don't handle
// click events falsely after a drag.
mInDrag = false;
mInCustomDrag = false;
onTouchEvent(ev);
return true;
}
if (!mInCustomDrag && !isEventOverChild(ev, mChildrenNeedingAllTouches)) {
return super.onInterceptTouchEvent(ev);
}
// Note the normal scrollview implementation is to intercept all touch events after it has
// detected a drag starting. We will handle this ourselves.
mInDrag = super.onInterceptTouchEvent(ev);
if (mInDrag) {
mInCustomDrag = super.onInterceptTouchEvent(ev);
if (mInCustomDrag) {
onTouchEvent(ev);
}
// Don't intercept events - pass them on to children as normal.
return false;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
excludeChildrenFromInterceptions(this);
}
/**
* Traverses the view tree for {@link WebView}s so they can be excluded from touch
* interceptions and receive all events.
*/
private void excludeChildrenFromInterceptions(View node) {
// If additional types of children should be excluded (e.g. horizontal scrolling banners),
// this needs to be modified accordingly.
if (node instanceof WebView) {
mChildrenNeedingAllTouches.add(node);
} else if (node instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup) node;
final int childCount = viewGroup.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = viewGroup.getChildAt(i);
excludeChildrenFromInterceptions(child);
}
}
}
private static final Rect sHitFrame = new Rect();
private static boolean isEventOverChild(MotionEvent ev, ArrayList<View> children) {
final int actionIndex = ev.getActionIndex();
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
for (View child : children) {
if (!canViewReceivePointerEvents(child)) {
continue;
}
child.getHitRect(sHitFrame);
// child can receive the motion event.
if (sHitFrame.contains((int) x, (int) y)) {
return true;
}
}
return false;
}
private static boolean canViewReceivePointerEvents(View child) {
return child.getVisibility() == VISIBLE || (child.getAnimation() != null);
}
}