From 98c968d178bfaaa756d86f0fa55518145d9b2126 Mon Sep 17 00:00:00 2001 From: Ben Komalo Date: Fri, 14 Oct 2011 18:39:52 -0700 Subject: [PATCH] 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 --- .../email/view/NonLockingScrollView.java | 77 +++++++++++++++++-- 1 file changed, 71 insertions(+), 6 deletions(-) diff --git a/src/com/android/email/view/NonLockingScrollView.java b/src/com/android/email/view/NonLockingScrollView.java index d5098a3b4..832136c89 100644 --- a/src/com/android/email/view/NonLockingScrollView.java +++ b/src/com/android/email/view/NonLockingScrollView.java @@ -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 mChildrenNeedingAllTouches = new ArrayList(); @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 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); + } }