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); + } }