diff options
Diffstat (limited to 'core/java/android/widget/ScrollView.java')
-rw-r--r-- | core/java/android/widget/ScrollView.java | 240 |
1 files changed, 169 insertions, 71 deletions
diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 24d97a5..959e982 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -16,6 +16,8 @@ package android.widget; +import com.android.internal.R; + import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; @@ -30,8 +32,6 @@ import android.view.ViewGroup; import android.view.ViewParent; import android.view.animation.AnimationUtils; -import com.android.internal.R; - import java.util.List; /** @@ -51,8 +51,6 @@ import java.util.List; * <p>ScrollView only supports vertical scrolling. */ public class ScrollView extends FrameLayout { - static final String TAG = "ScrollView"; - static final int ANIMATED_SCROLL_GAP = 250; static final float MAX_SCROLL_FACTOR = 0.5f; @@ -114,6 +112,18 @@ public class ScrollView extends FrameLayout { private int mTouchSlop; private int mMinimumVelocity; private int mMaximumVelocity; + + /** + * ID of the active pointer. This is used to retain consistency during + * drags/flings if multiple pointers are used. + */ + private int mActivePointerId = INVALID_POINTER; + + /** + * Sentinel value for no current active pointer. + * Used by {@link #mActivePointerId}. + */ + private static final int INVALID_POINTER = -1; public ScrollView(Context context) { this(context, null); @@ -305,11 +315,7 @@ public class ScrollView extends FrameLayout { @Override public boolean dispatchKeyEvent(KeyEvent event) { // Let the focused view and/or our descendants get the key first - boolean handled = super.dispatchKeyEvent(event); - if (handled) { - return true; - } - return executeKeyEvent(event); + return super.dispatchKeyEvent(event) || executeKeyEvent(event); } /** @@ -324,7 +330,7 @@ public class ScrollView extends FrameLayout { mTempRect.setEmpty(); if (!canScroll()) { - if (isFocused()) { + if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) { View currentFocused = findFocus(); if (currentFocused == this) currentFocused = null; View nextFocused = FocusFinder.getInstance().findNextFocus(this, @@ -362,6 +368,18 @@ public class ScrollView extends FrameLayout { return handled; } + private boolean inChild(int x, int y) { + if (getChildCount() > 0) { + final int scrollY = mScrollY; + final View child = getChildAt(0); + return !(y < child.getTop() - scrollY + || y >= child.getBottom() - scrollY + || x < child.getLeft() + || x >= child.getRight()); + } + return false; + } + @Override public boolean onInterceptTouchEvent(MotionEvent ev) { /* @@ -380,15 +398,8 @@ public class ScrollView extends FrameLayout { return true; } - if (!canScroll()) { - mIsBeingDragged = false; - return false; - } - - final float y = ev.getY(); - - switch (action) { - case MotionEvent.ACTION_MOVE: + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_MOVE: { /* * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check * whether the user has moved far enough from his original down touch. @@ -398,15 +409,35 @@ public class ScrollView extends FrameLayout { * Locally do absolute value. mLastMotionY is set to the y value * of the down event. */ + final int activePointerId = mActivePointerId; + if (activePointerId == INVALID_POINTER) { + // If we don't have a valid id, the touch down wasn't on content. + break; + } + + final int pointerIndex = ev.findPointerIndex(activePointerId); + final float y = ev.getY(pointerIndex); final int yDiff = (int) Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop) { mIsBeingDragged = true; + mLastMotionY = y; } break; + } + + case MotionEvent.ACTION_DOWN: { + final float y = ev.getY(); + if (!inChild((int) ev.getX(), (int) y)) { + mIsBeingDragged = false; + break; + } - case MotionEvent.ACTION_DOWN: - /* Remember location of down touch */ + /* + * Remember location of down touch. + * ACTION_DOWN always refers to pointer index 0. + */ mLastMotionY = y; + mActivePointerId = ev.getPointerId(0); /* * If being flinged and user touches the screen, initiate drag; @@ -415,11 +446,16 @@ public class ScrollView extends FrameLayout { */ mIsBeingDragged = !mScroller.isFinished(); break; + } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: /* Release the drag */ mIsBeingDragged = false; + mActivePointerId = INVALID_POINTER; + break; + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); break; } @@ -438,10 +474,6 @@ public class ScrollView extends FrameLayout { // descendants. return false; } - - if (!canScroll()) { - return false; - } if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); @@ -449,54 +481,100 @@ public class ScrollView extends FrameLayout { mVelocityTracker.addMovement(ev); final int action = ev.getAction(); - final float y = ev.getY(); - switch (action) { - case MotionEvent.ACTION_DOWN: + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: { + final float y = ev.getY(); + if (!(mIsBeingDragged = inChild((int) ev.getX(), (int) y))) { + return false; + } + /* - * If being flinged and user touches, stop the fling. isFinished - * will be false if being flinged. - */ + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ if (!mScroller.isFinished()) { mScroller.abortAnimation(); } // Remember where the motion event started mLastMotionY = y; + mActivePointerId = ev.getPointerId(0); break; + } case MotionEvent.ACTION_MOVE: - // Scroll to follow the motion event - final int deltaY = (int) (mLastMotionY - y); - mLastMotionY = y; - - if (deltaY < 0) { - if (mScrollY > 0) { - scrollBy(0, deltaY); - } - } else if (deltaY > 0) { - final int bottomEdge = getHeight() - mPaddingBottom; - final int availableToScroll = getChildAt(0).getBottom() - mScrollY - bottomEdge; - if (availableToScroll > 0) { - scrollBy(0, Math.min(availableToScroll, deltaY)); - } + if (mIsBeingDragged) { + // Scroll to follow the motion event + final int activePointerIndex = ev.findPointerIndex(mActivePointerId); + final float y = ev.getY(activePointerIndex); + final int deltaY = (int) (mLastMotionY - y); + mLastMotionY = y; + + scrollBy(0, deltaY); } break; - case MotionEvent.ACTION_UP: - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int initialVelocity = (int) velocityTracker.getYVelocity(); + case MotionEvent.ACTION_UP: + if (mIsBeingDragged) { + final VelocityTracker velocityTracker = mVelocityTracker; + velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); + int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId); + + if (getChildCount() > 0 && Math.abs(initialVelocity) > mMinimumVelocity) { + fling(-initialVelocity); + } - if ((Math.abs(initialVelocity) > mMinimumVelocity) && getChildCount() > 0) { - fling(-initialVelocity); - } + mActivePointerId = INVALID_POINTER; + mIsBeingDragged = false; - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + break; + case MotionEvent.ACTION_CANCEL: + if (mIsBeingDragged && getChildCount() > 0) { + mActivePointerId = INVALID_POINTER; + mIsBeingDragged = false; + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } } + break; + case MotionEvent.ACTION_POINTER_UP: + onSecondaryPointerUp(ev); + break; } return true; } + + private void onSecondaryPointerUp(MotionEvent ev) { + final int pointerIndex = (ev.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> + MotionEvent.ACTION_POINTER_INDEX_SHIFT; + final int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + // TODO: Make this decision more intelligent. + final int newPointerIndex = pointerIndex == 0 ? 1 : 0; + mLastMotionY = ev.getY(newPointerIndex); + mActivePointerId = ev.getPointerId(newPointerIndex); + if (mVelocityTracker != null) { + mVelocityTracker.clear(); + } + } + } + + private int getScrollRange() { + int scrollRange = 0; + if (getChildCount() > 0) { + View child = getChildAt(0); + scrollRange = Math.max(0, + child.getHeight() - getHeight() - mPaddingBottom - mPaddingTop); + } + return scrollRange; + } /** * <p> @@ -829,10 +907,19 @@ public class ScrollView extends FrameLayout { * @param dy the number of pixels to scroll by on the Y axis */ public final void smoothScrollBy(int dx, int dy) { + if (getChildCount() == 0) { + // Nothing to do. + return; + } long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; if (duration > ANIMATED_SCROLL_GAP) { - mScroller.startScroll(mScrollX, mScrollY, dx, dy); - awakenScrollBars(mScroller.getDuration()); + final int height = getHeight() - mPaddingBottom - mPaddingTop; + final int bottom = getChildAt(0).getHeight(); + final int maxY = Math.max(0, bottom - height); + final int scrollY = mScrollY; + dy = Math.max(0, Math.min(scrollY + dy, maxY)) - scrollY; + + mScroller.startScroll(mScrollX, scrollY, 0, dy); invalidate(); } else { if (!mScroller.isFinished()) { @@ -859,10 +946,19 @@ public class ScrollView extends FrameLayout { */ @Override protected int computeVerticalScrollRange() { - int count = getChildCount(); - return count == 0 ? getHeight() : (getChildAt(0)).getBottom(); + final int count = getChildCount(); + final int contentHeight = getHeight() - mPaddingBottom - mPaddingTop; + if (count == 0) { + return contentHeight; + } + + return getChildAt(0).getBottom(); } + @Override + protected int computeVerticalScrollOffset() { + return Math.max(0, super.computeVerticalScrollOffset()); + } @Override protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { @@ -916,18 +1012,19 @@ public class ScrollView extends FrameLayout { int oldY = mScrollY; int x = mScroller.getCurrX(); int y = mScroller.getCurrY(); + if (getChildCount() > 0) { View child = getChildAt(0); - mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth()); - mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight()); - } else { - mScrollX = x; - mScrollY = y; - } - if (oldX != mScrollX || oldY != mScrollY) { - onScrollChanged(mScrollX, mScrollY, oldX, oldY); + x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth()); + y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight()); + if (x != oldX || y != oldY) { + mScrollX = x; + mScrollY = y; + onScrollChanged(x, y, oldX, oldY); + } } - + awakenScrollBars(); + // Keep on drawing until the animation has finished. postInvalidate(); } @@ -1152,7 +1249,7 @@ public class ScrollView extends FrameLayout { * Fling the scroll view * * @param velocityY The initial velocity in the Y direction. Positive - * numbers mean that the finger/curor is moving down the screen, + * numbers mean that the finger/cursor is moving down the screen, * which means we want to scroll towards the top. */ public void fling(int velocityY) { @@ -1160,7 +1257,8 @@ public class ScrollView extends FrameLayout { int height = getHeight() - mPaddingBottom - mPaddingTop; int bottom = getChildAt(0).getHeight(); - mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height); + mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, + Math.max(0, bottom - height)); final boolean movingDown = velocityY > 0; @@ -1176,7 +1274,6 @@ public class ScrollView extends FrameLayout { mScrollViewMovedFocus = false; } - awakenScrollBars(mScroller.getDuration()); invalidate(); } } @@ -1186,6 +1283,7 @@ public class ScrollView extends FrameLayout { * * <p>This version also clamps the scrolling to the bounds of our child. */ + @Override public void scrollTo(int x, int y) { // we rely on the fact the View.scrollBy calls scrollTo. if (getChildCount() > 0) { |