HorizontalScrollView multitouch scroll and only grip on content

AbsListView multitouch scroll and only grip on content
This commit is contained in:
Adam Powell
2010-02-25 11:21:14 -08:00
parent 5be65ab5b9
commit 4cd47702f0
5 changed files with 206 additions and 51 deletions

View File

@@ -467,6 +467,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// True when the popup should be hidden because of a call to
// dispatchDisplayHint()
private boolean mPopupHidden;
/**
* 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;
/**
* Interface definition for a callback to be invoked when the list or grid
@@ -1995,8 +2007,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
final int action = ev.getAction();
final int x = (int) ev.getX();
final int y = (int) ev.getY();
View v;
int deltaY;
@@ -2006,18 +2016,22 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
mVelocityTracker.addMovement(ev);
switch (action) {
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
switch (mTouchMode) {
case TOUCH_MODE_OVERFLING: {
mFlingRunnable.endFling();
mTouchMode = TOUCH_MODE_OVERSCROLL;
mLastY = y;
mLastY = (int) ev.getY();
mMotionCorrection = 0;
mActivePointerId = ev.getPointerId(0);
break;
}
default: {
mActivePointerId = ev.getPointerId(0);
final int x = (int) ev.getX();
final int y = (int) ev.getY();
int motionPosition = pointToPosition(x, y);
if (!mDataChanged) {
if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
@@ -2037,12 +2051,15 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// code in ViewRoot to try to find a nearby view to select
return false;
}
// User clicked on whitespace, or stopped a fling. It is a scroll.
createScrollingCache();
mTouchMode = TOUCH_MODE_SCROLL;
mMotionCorrection = 0;
motionPosition = findMotionRow(y);
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
if (mTouchMode == TOUCH_MODE_FLING) {
// Stopped a fling. It is a scroll.
createScrollingCache();
mTouchMode = TOUCH_MODE_SCROLL;
mMotionCorrection = 0;
motionPosition = findMotionRow(y);
reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
}
}
}
@@ -2062,6 +2079,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
case MotionEvent.ACTION_MOVE: {
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final int y = (int) ev.getY(pointerIndex);
deltaY = y - mMotionY;
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
@@ -2142,7 +2161,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
// We did not scroll the full amount. Treat this essentially like the
// start of a new touch scroll
final int motionPosition = findMotionRow(y);
final int motionPosition = findClosestMotionRow(y);
mMotionCorrection = 0;
motionView = getChildAt(motionPosition - mFirstPosition);
@@ -2238,7 +2257,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
} else {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final int initialVelocity = (int) velocityTracker.getYVelocity();
final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
if (Math.abs(initialVelocity) > mMinimumVelocity) {
if (mFlingRunnable == null) {
@@ -2264,7 +2283,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
final int initialVelocity = (int) velocityTracker.getYVelocity();
final int initialVelocity = (int) velocityTracker.getYVelocity(mActivePointerId);
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
if (Math.abs(initialVelocity) > mMinimumVelocity) {
@@ -2290,6 +2309,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mVelocityTracker.recycle();
mVelocityTracker = null;
}
mActivePointerId = INVALID_POINTER;
if (PROFILE_SCROLLING) {
if (mScrollProfilingStarted) {
@@ -2332,6 +2353,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
mVelocityTracker = null;
}
}
mActivePointerId = INVALID_POINTER;
break;
}
case MotionEvent.ACTION_POINTER_UP: {
onSecondaryPointerUp(ev);
final int x = mMotionX;
final int y = mMotionY;
final int motionPosition = pointToPosition(x, y);
if (motionPosition >= 0) {
// Remember where the motion event started
v = getChildAt(motionPosition - mFirstPosition);
mMotionViewOriginalTop = v.getTop();
mMotionPosition = motionPosition;
}
mLastY = y;
break;
}
}
@@ -2380,8 +2419,6 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
int action = ev.getAction();
int x = (int) ev.getX();
int y = (int) ev.getY();
View v;
if (mFastScroller != null) {
@@ -2391,13 +2428,17 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
}
}
switch (action) {
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
int touchMode = mTouchMode;
if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
return true;
}
final int x = (int) ev.getX();
final int y = (int) ev.getY();
mActivePointerId = ev.getPointerId(0);
int motionPosition = findMotionRow(y);
if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
// User clicked on an actual view (and was not stopping a fling).
@@ -2420,6 +2461,8 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case MotionEvent.ACTION_MOVE: {
switch (mTouchMode) {
case TOUCH_MODE_DOWN:
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final int y = (int) ev.getY(pointerIndex);
if (startScrollIfNeeded(y - mMotionY)) {
return true;
}
@@ -2430,13 +2473,37 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
case MotionEvent.ACTION_UP: {
mTouchMode = TOUCH_MODE_REST;
mActivePointerId = INVALID_POINTER;
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
break;
}
case MotionEvent.ACTION_POINTER_UP: {
onSecondaryPointerUp(ev);
break;
}
}
return false;
}
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;
mMotionX = (int) ev.getX(newPointerIndex);
mMotionY = (int) ev.getY(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
/**
* {@inheritDoc}
@@ -3150,9 +3217,25 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
* Find the row closest to y. This row will be used as the motion row when scrolling
*
* @param y Where the user touched
* @return The position of the first (or only) item in the row closest to y
* @return The position of the first (or only) item in the row containing y
*/
abstract int findMotionRow(int y);
/**
* Find the row closest to y. This row will be used as the motion row when scrolling.
*
* @param y Where the user touched
* @return The position of the first (or only) item in the row closest to y
*/
int findClosestMotionRow(int y) {
final int childCount = getChildCount();
if (childCount == 0) {
return INVALID_POSITION;
}
final int motionRow = findMotionRow(y);
return motionRow != INVALID_POSITION ? motionRow : mFirstPosition + childCount - 1;
}
/**
* Causes all the views to be rebuilt and redrawn.

View File

@@ -431,8 +431,6 @@ public class GridView extends AbsListView {
}
}
}
return mFirstPosition + childCount - 1;
}
return INVALID_POSITION;
}

View File

@@ -116,7 +116,19 @@ public class HorizontalScrollView 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 HorizontalScrollView(Context context) {
this(context, null);
}
@@ -362,6 +374,17 @@ public class HorizontalScrollView extends FrameLayout {
return handled;
}
private boolean inChild(int x, int y) {
if (getChildCount() > 0) {
final View child = getChildAt(0);
return !(y < child.getTop()
|| y >= child.getBottom()
|| x < child.getLeft()
|| x >= child.getRight());
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
/*
@@ -380,10 +403,8 @@ public class HorizontalScrollView extends FrameLayout {
return true;
}
final float x = ev.getX();
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.
@@ -393,16 +414,30 @@ public class HorizontalScrollView extends FrameLayout {
* Locally do absolute value. mLastMotionX is set to the x value
* of the down event.
*/
final int pointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(pointerIndex);
final int xDiff = (int) Math.abs(x - mLastMotionX);
if (xDiff > mTouchSlop) {
mIsBeingDragged = true;
mLastMotionX = x;
if (mParent != null) mParent.requestDisallowInterceptTouchEvent(true);
}
break;
}
case MotionEvent.ACTION_DOWN:
/* Remember location of down touch */
case MotionEvent.ACTION_DOWN: {
final float x = ev.getX();
if (!inChild((int) x, (int) ev.getY())) {
mIsBeingDragged = false;
break;
}
/*
* Remember location of down touch.
* ACTION_DOWN always refers to pointer index 0.
*/
mLastMotionX = x;
mActivePointerId = ev.getPointerId(0);
/*
* If being flinged and user touches the screen, initiate drag;
@@ -411,11 +446,16 @@ public class HorizontalScrollView 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;
}
@@ -441,10 +481,9 @@ public class HorizontalScrollView extends FrameLayout {
mVelocityTracker.addMovement(ev);
final int action = ev.getAction();
final float x = ev.getX();
switch (action) {
case MotionEvent.ACTION_DOWN:
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN: {
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
@@ -452,42 +491,78 @@ public class HorizontalScrollView extends FrameLayout {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
final float x = ev.getX();
if (!(mIsBeingDragged = inChild((int) x, (int) ev.getY()))) {
return false;
}
// Remember where the motion event started
mLastMotionX = x;
break;
}
case MotionEvent.ACTION_MOVE:
// Scroll to follow the motion event
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
if (mIsBeingDragged) {
// Scroll to follow the motion event
final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
final float x = ev.getX(activePointerIndex);
final int deltaX = (int) (mLastMotionX - x);
mLastMotionX = x;
overscrollBy(deltaX, 0, mScrollX, 0, getScrollRange(), 0,
getOverscrollMax(), 0);
overscrollBy(deltaX, 0, mScrollX, 0, getScrollRange(), 0,
getOverscrollMax(), 0);
}
break;
case MotionEvent.ACTION_UP:
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity();
if (mIsBeingDragged) {
final VelocityTracker velocityTracker = mVelocityTracker;
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
int initialVelocity = (int) velocityTracker.getXVelocity(mActivePointerId);
if (getChildCount() > 0) {
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
fling(-initialVelocity);
} else {
final int right = getScrollRange();
if (mScroller.springback(mScrollX, mScrollY, 0, 0, right, 0)) {
invalidate();
if (getChildCount() > 0) {
if ((Math.abs(initialVelocity) > mMinimumVelocity)) {
fling(-initialVelocity);
} else {
final int right = getScrollRange();
if (mScroller.springback(mScrollX, mScrollY, 0, 0, right, 0)) {
invalidate();
}
}
}
}
mActivePointerId = INVALID_POINTER;
mIsBeingDragged = false;
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
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;
mLastMotionX = ev.getX(newPointerIndex);
mActivePointerId = ev.getPointerId(newPointerIndex);
if (mVelocityTracker != null) {
mVelocityTracker.clear();
}
}
}
@Override
protected void onOverscrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {

View File

@@ -1192,7 +1192,6 @@ public class ListView extends AbsListView {
return mFirstPosition + i;
}
}
return mFirstPosition + childCount - 1;
}
return INVALID_POSITION;
}

View File

@@ -427,7 +427,7 @@ public class ScrollView extends FrameLayout {
case MotionEvent.ACTION_DOWN: {
final float y = ev.getY();
if (!inChild((int)ev.getX(), (int)y)) {
if (!inChild((int) ev.getX(), (int) y)) {
mIsBeingDragged = false;
break;
}
@@ -493,7 +493,7 @@ public class ScrollView extends FrameLayout {
}
final float y = ev.getY();
if (!(mIsBeingDragged = inChild((int)ev.getX(), (int)y))) {
if (!(mIsBeingDragged = inChild((int) ev.getX(), (int) y))) {
return false;
}