summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget/ScrollView.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/widget/ScrollView.java')
-rw-r--r--core/java/android/widget/ScrollView.java240
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) {