OSDN Git Service

Handle DOWN/MOVE/UP gestures with fling.
authorJeff Brown <jeffbrown@google.com>
Sat, 10 Sep 2011 00:15:31 +0000 (17:15 -0700)
committerJeff Brown <jeffbrown@google.com>
Sat, 10 Sep 2011 00:15:31 +0000 (17:15 -0700)
Bug: 5265814

When MOVE events are batched, a ListView may not receive only
one MOVE event followed immediately by an UP in the case of
a quick fling.  Unfortunately, the old code relied on there
being at least one additional MOVE event following the first
in order to really scroll or fling.

This change extracts common code from onTouchEvent to
handle scrolls as soon as they are started if there is additional
remaining motion beyond the initial touch slop (taken care of by
mMotionCorrection).

Change-Id: I8c8f03f580bfba96aa69cf111d245ba7fd3b4c2a

core/java/android/widget/AbsListView.java

index 05d4f05..d7fb7a0 100644 (file)
@@ -2771,15 +2771,21 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
         }
     }
 
-    private boolean startScrollIfNeeded(int deltaY) {
+    private boolean startScrollIfNeeded(int y) {
         // Check if we have moved far enough that it looks more like a
         // scroll than a tap
+        final int deltaY = y - mMotionY;
         final int distance = Math.abs(deltaY);
         final boolean overscroll = mScrollY != 0;
         if (overscroll || distance > mTouchSlop) {
             createScrollingCache();
-            mTouchMode = overscroll ? TOUCH_MODE_OVERSCROLL : TOUCH_MODE_SCROLL;
-            mMotionCorrection = deltaY;
+            if (overscroll) {
+                mTouchMode = TOUCH_MODE_OVERSCROLL;
+                mMotionCorrection = 0;
+            } else {
+                mTouchMode = TOUCH_MODE_SCROLL;
+                mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
+            }
             final Handler handler = getHandler();
             // Handler should not be null unless the AbsListView is not attached to a
             // window, which would make it very hard to scroll it... but the monkeys
@@ -2799,12 +2805,176 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
             if (parent != null) {
                 parent.requestDisallowInterceptTouchEvent(true);
             }
+            scrollIfNeeded(y);
             return true;
         }
 
         return false;
     }
 
+    private void scrollIfNeeded(int y) {
+        final int rawDeltaY = y - mMotionY;
+        final int deltaY = rawDeltaY - mMotionCorrection;
+        int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
+
+        if (mTouchMode == TOUCH_MODE_SCROLL) {
+            if (PROFILE_SCROLLING) {
+                if (!mScrollProfilingStarted) {
+                    Debug.startMethodTracing("AbsListViewScroll");
+                    mScrollProfilingStarted = true;
+                }
+            }
+
+            if (mScrollStrictSpan == null) {
+                // If it's non-null, we're already in a scroll.
+                mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
+            }
+
+            if (y != mLastY) {
+                // We may be here after stopping a fling and continuing to scroll.
+                // If so, we haven't disallowed intercepting touch events yet.
+                // Make sure that we do so in case we're in a parent that can intercept.
+                if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
+                        Math.abs(rawDeltaY) > mTouchSlop) {
+                    final ViewParent parent = getParent();
+                    if (parent != null) {
+                        parent.requestDisallowInterceptTouchEvent(true);
+                    }
+                }
+
+                final int motionIndex;
+                if (mMotionPosition >= 0) {
+                    motionIndex = mMotionPosition - mFirstPosition;
+                } else {
+                    // If we don't have a motion position that we can reliably track,
+                    // pick something in the middle to make a best guess at things below.
+                    motionIndex = getChildCount() / 2;
+                }
+
+                int motionViewPrevTop = 0;
+                View motionView = this.getChildAt(motionIndex);
+                if (motionView != null) {
+                    motionViewPrevTop = motionView.getTop();
+                }
+
+                // No need to do all this work if we're not going to move anyway
+                boolean atEdge = false;
+                if (incrementalDeltaY != 0) {
+                    atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
+                }
+
+                // Check to see if we have bumped into the scroll limit
+                motionView = this.getChildAt(motionIndex);
+                if (motionView != null) {
+                    // Check if the top of the motion view is where it is
+                    // supposed to be
+                    final int motionViewRealTop = motionView.getTop();
+                    if (atEdge) {
+                        // Apply overscroll
+
+                        int overscroll = -incrementalDeltaY -
+                                (motionViewRealTop - motionViewPrevTop);
+                        overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
+                                0, mOverscrollDistance, true);
+                        if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
+                            // Don't allow overfling if we're at the edge.
+                            if (mVelocityTracker != null) {
+                                mVelocityTracker.clear();
+                            }
+                        }
+
+                        final int overscrollMode = getOverScrollMode();
+                        if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                                        !contentFits())) {
+                            mDirection = 0; // Reset when entering overscroll.
+                            mTouchMode = TOUCH_MODE_OVERSCROLL;
+                            if (rawDeltaY > 0) {
+                                mEdgeGlowTop.onPull((float) overscroll / getHeight());
+                                if (!mEdgeGlowBottom.isFinished()) {
+                                    mEdgeGlowBottom.onRelease();
+                                }
+                            } else if (rawDeltaY < 0) {
+                                mEdgeGlowBottom.onPull((float) overscroll / getHeight());
+                                if (!mEdgeGlowTop.isFinished()) {
+                                    mEdgeGlowTop.onRelease();
+                                }
+                            }
+                        }
+                    }
+                    mMotionY = y;
+                    invalidate();
+                }
+                mLastY = y;
+            }
+        } else if (mTouchMode == TOUCH_MODE_OVERSCROLL) {
+            if (y != mLastY) {
+                final int oldScroll = mScrollY;
+                final int newScroll = oldScroll - incrementalDeltaY;
+                int newDirection = y > mLastY ? 1 : -1;
+
+                if (mDirection == 0) {
+                    mDirection = newDirection;
+                }
+
+                int overScrollDistance = -incrementalDeltaY;
+                if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
+                    overScrollDistance = -oldScroll;
+                    incrementalDeltaY += overScrollDistance;
+                } else {
+                    incrementalDeltaY = 0;
+                }
+
+                if (overScrollDistance != 0) {
+                    overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
+                            0, mOverscrollDistance, true);
+                    final int overscrollMode = getOverScrollMode();
+                    if (overscrollMode == OVER_SCROLL_ALWAYS ||
+                            (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
+                                    !contentFits())) {
+                        if (rawDeltaY > 0) {
+                            mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
+                            if (!mEdgeGlowBottom.isFinished()) {
+                                mEdgeGlowBottom.onRelease();
+                            }
+                        } else if (rawDeltaY < 0) {
+                            mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
+                            if (!mEdgeGlowTop.isFinished()) {
+                                mEdgeGlowTop.onRelease();
+                            }
+                        }
+                        invalidate();
+                    }
+                }
+
+                if (incrementalDeltaY != 0) {
+                    // Coming back to 'real' list scrolling
+                    mScrollY = 0;
+                    invalidateParentIfNeeded();
+
+                    // No need to do all this work if we're not going to move anyway
+                    if (incrementalDeltaY != 0) {
+                        trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
+                    }
+
+                    mTouchMode = TOUCH_MODE_SCROLL;
+
+                    // We did not scroll the full amount. Treat this essentially like the
+                    // start of a new touch scroll
+                    final int motionPosition = findClosestMotionRow(y);
+
+                    mMotionCorrection = 0;
+                    View motionView = getChildAt(motionPosition - mFirstPosition);
+                    mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
+                    mMotionY = y;
+                    mMotionPosition = motionPosition;
+                }
+                mLastY = y;
+                mDirection = newDirection;
+            }
+        }
+    }
+
     public void onTouchModeChanged(boolean isInTouchMode) {
         if (isInTouchMode) {
             // Get rid of the selection when we enter touch mode
@@ -2856,7 +3026,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
         final int action = ev.getAction();
 
         View v;
-        int deltaY;
 
         initVelocityTrackerIfNotExists();
         mVelocityTracker.addMovement(ev);
@@ -2935,183 +3104,19 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                 mActivePointerId = ev.getPointerId(pointerIndex);
             }
             final int y = (int) ev.getY(pointerIndex);
-            deltaY = y - mMotionY;
             switch (mTouchMode) {
             case TOUCH_MODE_DOWN:
             case TOUCH_MODE_TAP:
             case TOUCH_MODE_DONE_WAITING:
                 // Check if we have moved far enough that it looks more like a
                 // scroll than a tap
-                startScrollIfNeeded(deltaY);
+                startScrollIfNeeded(y);
                 break;
             case TOUCH_MODE_SCROLL:
-                if (PROFILE_SCROLLING) {
-                    if (!mScrollProfilingStarted) {
-                        Debug.startMethodTracing("AbsListViewScroll");
-                        mScrollProfilingStarted = true;
-                    }
-                }
-
-                if (mScrollStrictSpan == null) {
-                    // If it's non-null, we're already in a scroll.
-                    mScrollStrictSpan = StrictMode.enterCriticalSpan("AbsListView-scroll");
-                }
-
-                if (y != mLastY) {
-                    // We may be here after stopping a fling and continuing to scroll.
-                    // If so, we haven't disallowed intercepting touch events yet.
-                    // Make sure that we do so in case we're in a parent that can intercept.
-                    if ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0 &&
-                            Math.abs(deltaY) > mTouchSlop) {
-                        final ViewParent parent = getParent();
-                        if (parent != null) {
-                            parent.requestDisallowInterceptTouchEvent(true);
-                        }
-                    }
-
-                    final int rawDeltaY = deltaY;
-                    deltaY -= mMotionCorrection;
-                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
-
-                    final int motionIndex;
-                    if (mMotionPosition >= 0) {
-                        motionIndex = mMotionPosition - mFirstPosition;
-                    } else {
-                        // If we don't have a motion position that we can reliably track,
-                        // pick something in the middle to make a best guess at things below.
-                        motionIndex = getChildCount() / 2;
-                    }
-
-                    int motionViewPrevTop = 0;
-                    View motionView = this.getChildAt(motionIndex);
-                    if (motionView != null) {
-                        motionViewPrevTop = motionView.getTop();
-                    }
-
-                    // No need to do all this work if we're not going to move anyway
-                    boolean atEdge = false;
-                    if (incrementalDeltaY != 0) {
-                        atEdge = trackMotionScroll(deltaY, incrementalDeltaY);
-                    }
-
-                    // Check to see if we have bumped into the scroll limit
-                    motionView = this.getChildAt(motionIndex);
-                    if (motionView != null) {
-                        // Check if the top of the motion view is where it is
-                        // supposed to be
-                        final int motionViewRealTop = motionView.getTop();
-                        if (atEdge) {
-                            // Apply overscroll
-
-                            int overscroll = -incrementalDeltaY -
-                                    (motionViewRealTop - motionViewPrevTop);
-                            overScrollBy(0, overscroll, 0, mScrollY, 0, 0,
-                                    0, mOverscrollDistance, true);
-                            if (Math.abs(mOverscrollDistance) == Math.abs(mScrollY)) {
-                                // Don't allow overfling if we're at the edge.
-                                if (mVelocityTracker != null) {
-                                    mVelocityTracker.clear();
-                                }
-                            }
-
-                            final int overscrollMode = getOverScrollMode();
-                            if (overscrollMode == OVER_SCROLL_ALWAYS ||
-                                    (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
-                                            !contentFits())) {
-                                mDirection = 0; // Reset when entering overscroll.
-                                mTouchMode = TOUCH_MODE_OVERSCROLL;
-                                if (rawDeltaY > 0) {
-                                    mEdgeGlowTop.onPull((float) overscroll / getHeight());
-                                    if (!mEdgeGlowBottom.isFinished()) {
-                                        mEdgeGlowBottom.onRelease();
-                                    }
-                                } else if (rawDeltaY < 0) {
-                                    mEdgeGlowBottom.onPull((float) overscroll / getHeight());
-                                    if (!mEdgeGlowTop.isFinished()) {
-                                        mEdgeGlowTop.onRelease();
-                                    }
-                                }
-                            }
-                        }
-                        mMotionY = y;
-                        invalidate();
-                    }
-                    mLastY = y;
-                }
-                break;
-
             case TOUCH_MODE_OVERSCROLL:
-                if (y != mLastY) {
-                    final int rawDeltaY = deltaY;
-                    deltaY -= mMotionCorrection;
-                    int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
-
-                    final int oldScroll = mScrollY;
-                    final int newScroll = oldScroll - incrementalDeltaY;
-                    int newDirection = y > mLastY ? 1 : -1;
-
-                    if (mDirection == 0) {
-                        mDirection = newDirection;
-                    }
-
-                    int overScrollDistance = -incrementalDeltaY;
-                    if ((newScroll < 0 && oldScroll >= 0) || (newScroll > 0 && oldScroll <= 0)) {
-                        overScrollDistance = -oldScroll;
-                        incrementalDeltaY += overScrollDistance;
-                    } else {
-                        incrementalDeltaY = 0;
-                    }
-
-                    if (overScrollDistance != 0) {
-                        overScrollBy(0, overScrollDistance, 0, mScrollY, 0, 0,
-                                0, mOverscrollDistance, true);
-                        final int overscrollMode = getOverScrollMode();
-                        if (overscrollMode == OVER_SCROLL_ALWAYS ||
-                                (overscrollMode == OVER_SCROLL_IF_CONTENT_SCROLLS &&
-                                        !contentFits())) {
-                            if (rawDeltaY > 0) {
-                                mEdgeGlowTop.onPull((float) overScrollDistance / getHeight());
-                                if (!mEdgeGlowBottom.isFinished()) {
-                                    mEdgeGlowBottom.onRelease();
-                                }
-                            } else if (rawDeltaY < 0) {
-                                mEdgeGlowBottom.onPull((float) overScrollDistance / getHeight());
-                                if (!mEdgeGlowTop.isFinished()) {
-                                    mEdgeGlowTop.onRelease();
-                                }
-                            }
-                            invalidate();
-                        }
-                    }
-
-                    if (incrementalDeltaY != 0) {
-                        // Coming back to 'real' list scrolling
-                        mScrollY = 0;
-                        invalidateParentIfNeeded();
-
-                        // No need to do all this work if we're not going to move anyway
-                        if (incrementalDeltaY != 0) {
-                            trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
-                        }
-
-                        mTouchMode = TOUCH_MODE_SCROLL;
-
-                        // We did not scroll the full amount. Treat this essentially like the
-                        // start of a new touch scroll
-                        final int motionPosition = findClosestMotionRow(y);
-
-                        mMotionCorrection = 0;
-                        View motionView = getChildAt(motionPosition - mFirstPosition);
-                        mMotionViewOriginalTop = motionView != null ? motionView.getTop() : 0;
-                        mMotionY = y;
-                        mMotionPosition = motionPosition;
-                    }
-                    mLastY = y;
-                    mDirection = newDirection;
-                }
+                scrollIfNeeded(y);
                 break;
             }
-
             break;
         }
 
@@ -3542,7 +3547,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
                 final int y = (int) ev.getY(pointerIndex);
                 initVelocityTrackerIfNotExists();
                 mVelocityTracker.addMovement(ev);
-                if (startScrollIfNeeded(y - mMotionY)) {
+                if (startScrollIfNeeded(y)) {
                     return true;
                 }
                 break;