Merge "Overscrolling in AbsListView; visibility cleanup for FlingRunnable"
This commit is contained in:
@@ -16,6 +16,8 @@
|
||||
|
||||
package android.widget;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
@@ -30,6 +32,7 @@ import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
@@ -41,14 +44,12 @@ import android.view.ViewConfiguration;
|
||||
import android.view.ViewDebug;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.inputmethod.BaseInputConnection;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
import android.view.inputmethod.InputConnectionWrapper;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
|
||||
import com.android.internal.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -120,12 +121,23 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
* Indicates the touch gesture is a scroll
|
||||
*/
|
||||
static final int TOUCH_MODE_SCROLL = 3;
|
||||
|
||||
|
||||
/**
|
||||
* Indicates the view is in the process of being flung
|
||||
*/
|
||||
static final int TOUCH_MODE_FLING = 4;
|
||||
|
||||
/**
|
||||
* Indicates the touch gesture is an overscroll - a scroll beyond the beginning or end.
|
||||
*/
|
||||
static final int TOUCH_MODE_OVERSCROLL = 5;
|
||||
|
||||
/**
|
||||
* Indicates the view is being flung outside of normal content bounds
|
||||
* and will spring back.
|
||||
*/
|
||||
static final int TOUCH_MODE_OVERFLING = 6;
|
||||
|
||||
/**
|
||||
* Regular layout - usually an unsolicited layout from the view system
|
||||
*/
|
||||
@@ -1915,6 +1927,14 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
mLayoutMode = LAYOUT_NORMAL;
|
||||
layoutChildren();
|
||||
}
|
||||
} else {
|
||||
int touchMode = mTouchMode;
|
||||
if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) {
|
||||
mScrollY = 0;
|
||||
if (mFlingRunnable != null) {
|
||||
mFlingRunnable.endFling();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1947,43 +1967,56 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
int motionPosition = pointToPosition(x, y);
|
||||
if (!mDataChanged) {
|
||||
if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
|
||||
&& (getAdapter().isEnabled(motionPosition))) {
|
||||
// User clicked on an actual view (and was not stopping a fling). It might be a
|
||||
// click or a scroll. Assume it is a click until proven otherwise
|
||||
mTouchMode = TOUCH_MODE_DOWN;
|
||||
// FIXME Debounce
|
||||
if (mPendingCheckForTap == null) {
|
||||
mPendingCheckForTap = new CheckForTap();
|
||||
switch (mTouchMode) {
|
||||
case TOUCH_MODE_OVERFLING: {
|
||||
mFlingRunnable.endFling();
|
||||
mTouchMode = TOUCH_MODE_OVERSCROLL;
|
||||
mLastY = y;
|
||||
mMotionCorrection = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
int motionPosition = pointToPosition(x, y);
|
||||
if (!mDataChanged) {
|
||||
if ((mTouchMode != TOUCH_MODE_FLING) && (motionPosition >= 0)
|
||||
&& (getAdapter().isEnabled(motionPosition))) {
|
||||
// User clicked on an actual view (and was not stopping a fling). It might be a
|
||||
// click or a scroll. Assume it is a click until proven otherwise
|
||||
mTouchMode = TOUCH_MODE_DOWN;
|
||||
// FIXME Debounce
|
||||
if (mPendingCheckForTap == null) {
|
||||
mPendingCheckForTap = new CheckForTap();
|
||||
}
|
||||
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
|
||||
} else {
|
||||
if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
|
||||
// If we couldn't find a view to click on, but the down event was touching
|
||||
// the edge, we will bail out and try again. This allows the edge correcting
|
||||
// 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);
|
||||
}
|
||||
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
|
||||
} else {
|
||||
if (ev.getEdgeFlags() != 0 && motionPosition < 0) {
|
||||
// If we couldn't find a view to click on, but the down event was touching
|
||||
// the edge, we will bail out and try again. This allows the edge correcting
|
||||
// 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 (motionPosition >= 0) {
|
||||
// Remember where the motion event started
|
||||
v = getChildAt(motionPosition - mFirstPosition);
|
||||
mMotionViewOriginalTop = v.getTop();
|
||||
mMotionX = x;
|
||||
mMotionY = y;
|
||||
mMotionPosition = motionPosition;
|
||||
if (motionPosition >= 0) {
|
||||
// Remember where the motion event started
|
||||
v = getChildAt(motionPosition - mFirstPosition);
|
||||
mMotionViewOriginalTop = v.getTop();
|
||||
mMotionX = x;
|
||||
mMotionY = y;
|
||||
mMotionPosition = motionPosition;
|
||||
}
|
||||
mLastY = Integer.MIN_VALUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
mLastY = Integer.MIN_VALUE;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -2008,17 +2041,61 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
if (y != mLastY) {
|
||||
deltaY -= mMotionCorrection;
|
||||
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
|
||||
|
||||
int motionViewPrevTop = 0;
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
motionViewPrevTop = motionView.getTop();
|
||||
}
|
||||
// No need to do all this work if we're not going to move anyway
|
||||
if (incrementalDeltaY != 0) {
|
||||
trackMotionScroll(deltaY, incrementalDeltaY);
|
||||
}
|
||||
|
||||
// Check to see if we have bumped into the scroll limit
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
// Check if the top of the motion view is where it is
|
||||
// supposed to be
|
||||
if (motionView.getTop() != mMotionViewNewTop) {
|
||||
final int motionViewRealTop = motionView.getTop();
|
||||
final int motionViewNewTop = mMotionViewNewTop;
|
||||
if (motionViewRealTop != motionViewNewTop) {
|
||||
// Apply overscroll
|
||||
|
||||
mScrollY -= incrementalDeltaY - (motionViewRealTop - motionViewPrevTop);
|
||||
mTouchMode = TOUCH_MODE_OVERSCROLL;
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
mLastY = y;
|
||||
}
|
||||
break;
|
||||
|
||||
case TOUCH_MODE_OVERSCROLL:
|
||||
if (y != mLastY) {
|
||||
deltaY -= mMotionCorrection;
|
||||
int incrementalDeltaY = mLastY != Integer.MIN_VALUE ? y - mLastY : deltaY;
|
||||
|
||||
final int oldScroll = mScrollY;
|
||||
final int newScroll = oldScroll - incrementalDeltaY;
|
||||
|
||||
if ((oldScroll >= 0 && newScroll <= 0) ||
|
||||
(oldScroll <= 0 && newScroll >= 0)) {
|
||||
// Coming back to 'real' list scrolling
|
||||
incrementalDeltaY = -newScroll;
|
||||
mScrollY = 0;
|
||||
|
||||
// No need to do all this work if we're not going to move anyway
|
||||
if (incrementalDeltaY != 0) {
|
||||
trackMotionScroll(incrementalDeltaY, incrementalDeltaY);
|
||||
}
|
||||
|
||||
// Check to see if we are back in
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
int topOffset = motionView.getTop() - mMotionViewNewTop;
|
||||
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 = findMotionRow(y);
|
||||
@@ -2029,6 +2106,9 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
mMotionY = y;
|
||||
mMotionPosition = motionPosition;
|
||||
}
|
||||
} else {
|
||||
mScrollY -= incrementalDeltaY;
|
||||
invalidate();
|
||||
}
|
||||
mLastY = y;
|
||||
}
|
||||
@@ -2120,6 +2200,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
mFlingRunnable = new FlingRunnable();
|
||||
}
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
|
||||
|
||||
mFlingRunnable.start(-initialVelocity);
|
||||
} else {
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
@@ -2130,6 +2211,24 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
|
||||
}
|
||||
break;
|
||||
|
||||
case TOUCH_MODE_OVERSCROLL:
|
||||
if (mFlingRunnable == null) {
|
||||
mFlingRunnable = new FlingRunnable();
|
||||
}
|
||||
final VelocityTracker velocityTracker = mVelocityTracker;
|
||||
velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
|
||||
final int initialVelocity = (int) velocityTracker.getYVelocity();
|
||||
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_FLING);
|
||||
if (Math.abs(initialVelocity) > mMinimumVelocity) {
|
||||
mFlingRunnable.startOverfling(-initialVelocity);
|
||||
} else {
|
||||
mFlingRunnable.startSpringback();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
setPressed(false);
|
||||
@@ -2157,25 +2256,38 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
}
|
||||
|
||||
case MotionEvent.ACTION_CANCEL: {
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
setPressed(false);
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
motionView.setPressed(false);
|
||||
}
|
||||
clearScrollingCache();
|
||||
switch (mTouchMode) {
|
||||
case TOUCH_MODE_OVERSCROLL:
|
||||
if (mFlingRunnable == null) {
|
||||
mFlingRunnable = new FlingRunnable();
|
||||
}
|
||||
mFlingRunnable.startSpringback();
|
||||
break;
|
||||
|
||||
case TOUCH_MODE_OVERFLING:
|
||||
// Do nothing - let it play out.
|
||||
break;
|
||||
|
||||
default:
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
setPressed(false);
|
||||
View motionView = this.getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
motionView.setPressed(false);
|
||||
}
|
||||
clearScrollingCache();
|
||||
|
||||
final Handler handler = getHandler();
|
||||
if (handler != null) {
|
||||
handler.removeCallbacks(mPendingCheckForLongPress);
|
||||
}
|
||||
final Handler handler = getHandler();
|
||||
if (handler != null) {
|
||||
handler.removeCallbacks(mPendingCheckForLongPress);
|
||||
}
|
||||
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
if (mVelocityTracker != null) {
|
||||
mVelocityTracker.recycle();
|
||||
mVelocityTracker = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -2205,8 +2317,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
int touchMode = mTouchMode;
|
||||
if (touchMode == TOUCH_MODE_OVERFLING || touchMode == TOUCH_MODE_OVERSCROLL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int motionPosition = findMotionRow(y);
|
||||
if (mTouchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
|
||||
if (touchMode != TOUCH_MODE_FLING && motionPosition >= 0) {
|
||||
// User clicked on an actual view (and was not stopping a fling).
|
||||
// Remember where the motion event started
|
||||
v = getChildAt(motionPosition - mFirstPosition);
|
||||
@@ -2218,7 +2335,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
clearScrollingCache();
|
||||
}
|
||||
mLastY = Integer.MIN_VALUE;
|
||||
if (mTouchMode == TOUCH_MODE_FLING) {
|
||||
if (touchMode == TOUCH_MODE_FLING) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
@@ -2293,18 +2410,18 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
/**
|
||||
* Tracks the decay of a fling scroll
|
||||
*/
|
||||
private Scroller mScroller;
|
||||
private OverScroller mScroller;
|
||||
|
||||
/**
|
||||
* Y value reported by mScroller on the previous fling
|
||||
*/
|
||||
private int mLastFlingY;
|
||||
|
||||
public FlingRunnable() {
|
||||
mScroller = new Scroller(getContext());
|
||||
FlingRunnable() {
|
||||
mScroller = new OverScroller(getContext());
|
||||
}
|
||||
|
||||
public void start(int initialVelocity) {
|
||||
void start(int initialVelocity) {
|
||||
int initialY = initialVelocity < 0 ? Integer.MAX_VALUE : 0;
|
||||
mLastFlingY = initialY;
|
||||
mScroller.fling(0, initialY, 0, initialVelocity,
|
||||
@@ -2319,77 +2436,117 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void startSpringback() {
|
||||
if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) {
|
||||
mTouchMode = TOUCH_MODE_OVERFLING;
|
||||
invalidate();
|
||||
post(this);
|
||||
}
|
||||
}
|
||||
|
||||
void startOverfling(int initialVelocity) {
|
||||
mScroller.fling(0, mScrollY, 0, initialVelocity, 0, 0, 0, 0, 0, getHeight());
|
||||
mTouchMode = TOUCH_MODE_OVERFLING;
|
||||
invalidate();
|
||||
post(this);
|
||||
}
|
||||
|
||||
private void endFling() {
|
||||
mTouchMode = TOUCH_MODE_REST;
|
||||
reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE);
|
||||
clearScrollingCache();
|
||||
removeCallbacks(this);
|
||||
}
|
||||
|
||||
public void run() {
|
||||
if (mTouchMode != TOUCH_MODE_FLING) {
|
||||
switch (mTouchMode) {
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (mItemCount == 0 || getChildCount() == 0) {
|
||||
endFling();
|
||||
return;
|
||||
}
|
||||
|
||||
final Scroller scroller = mScroller;
|
||||
boolean more = scroller.computeScrollOffset();
|
||||
final int y = scroller.getCurrY();
|
||||
|
||||
// Flip sign to convert finger direction to list items direction
|
||||
// (e.g. finger moving down means list is moving towards the top)
|
||||
int delta = mLastFlingY - y;
|
||||
|
||||
// Pretend that each frame of a fling scroll is a touch scroll
|
||||
if (delta > 0) {
|
||||
// List is moving towards the top. Use first view as mMotionPosition
|
||||
mMotionPosition = mFirstPosition;
|
||||
final View firstView = getChildAt(0);
|
||||
mMotionViewOriginalTop = firstView.getTop();
|
||||
|
||||
// Don't fling more than 1 screen
|
||||
delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
|
||||
} else {
|
||||
// List is moving towards the bottom. Use last view as mMotionPosition
|
||||
int offsetToLast = getChildCount() - 1;
|
||||
mMotionPosition = mFirstPosition + offsetToLast;
|
||||
|
||||
final View lastView = getChildAt(offsetToLast);
|
||||
mMotionViewOriginalTop = lastView.getTop();
|
||||
|
||||
// Don't fling more than 1 screen
|
||||
delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
|
||||
}
|
||||
|
||||
trackMotionScroll(delta, delta);
|
||||
|
||||
// Check to see if we have bumped into the scroll limit
|
||||
View motionView = getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
// Check if the top of the motion view is where it is
|
||||
// supposed to be
|
||||
if (motionView.getTop() != mMotionViewNewTop) {
|
||||
more = false;
|
||||
|
||||
case TOUCH_MODE_FLING: {
|
||||
if (mItemCount == 0 || getChildCount() == 0) {
|
||||
endFling();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (more) {
|
||||
invalidate();
|
||||
mLastFlingY = y;
|
||||
post(this);
|
||||
} else {
|
||||
endFling();
|
||||
if (PROFILE_FLINGING) {
|
||||
if (mFlingProfilingStarted) {
|
||||
Debug.stopMethodTracing();
|
||||
mFlingProfilingStarted = false;
|
||||
final OverScroller scroller = mScroller;
|
||||
boolean more = scroller.computeScrollOffset();
|
||||
final int y = scroller.getCurrY();
|
||||
|
||||
// Flip sign to convert finger direction to list items direction
|
||||
// (e.g. finger moving down means list is moving towards the top)
|
||||
int delta = mLastFlingY - y;
|
||||
|
||||
// Pretend that each frame of a fling scroll is a touch scroll
|
||||
if (delta > 0) {
|
||||
// List is moving towards the top. Use first view as mMotionPosition
|
||||
mMotionPosition = mFirstPosition;
|
||||
final View firstView = getChildAt(0);
|
||||
mMotionViewOriginalTop = firstView.getTop();
|
||||
|
||||
// Don't fling more than 1 screen
|
||||
delta = Math.min(getHeight() - mPaddingBottom - mPaddingTop - 1, delta);
|
||||
} else {
|
||||
// List is moving towards the bottom. Use last view as mMotionPosition
|
||||
int offsetToLast = getChildCount() - 1;
|
||||
mMotionPosition = mFirstPosition + offsetToLast;
|
||||
|
||||
final View lastView = getChildAt(offsetToLast);
|
||||
mMotionViewOriginalTop = lastView.getTop();
|
||||
|
||||
// Don't fling more than 1 screen
|
||||
delta = Math.max(-(getHeight() - mPaddingBottom - mPaddingTop - 1), delta);
|
||||
}
|
||||
|
||||
// Do something different on overscroll - offsetChildrenTopAndBottom()
|
||||
trackMotionScroll(delta, delta);
|
||||
|
||||
// Check to see if we have bumped into the scroll limit
|
||||
View motionView = getChildAt(mMotionPosition - mFirstPosition);
|
||||
if (motionView != null) {
|
||||
// Check if the top of the motion view is where it is
|
||||
// supposed to be
|
||||
if (motionView.getTop() != mMotionViewNewTop) {
|
||||
float vel = scroller.getCurrVelocity();
|
||||
if (delta > 0) {
|
||||
vel = -vel;
|
||||
}
|
||||
startOverfling(Math.round(vel));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (more) {
|
||||
invalidate();
|
||||
mLastFlingY = y;
|
||||
post(this);
|
||||
} else {
|
||||
endFling();
|
||||
|
||||
if (PROFILE_FLINGING) {
|
||||
if (mFlingProfilingStarted) {
|
||||
Debug.stopMethodTracing();
|
||||
mFlingProfilingStarted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOUCH_MODE_OVERFLING: {
|
||||
final OverScroller scroller = mScroller;
|
||||
if (scroller.computeScrollOffset()) {
|
||||
mScrollY = scroller.getCurrY();
|
||||
invalidate();
|
||||
post(this);
|
||||
} else {
|
||||
endFling();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -383,4 +383,15 @@ public class OverScroller {
|
||||
public int getFinalY() {
|
||||
return mCurrScroller.getFinalY();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Returns the current velocity.
|
||||
*
|
||||
* @return The original velocity less the deceleration. Result may be
|
||||
* negative.
|
||||
*/
|
||||
public float getCurrVelocity() {
|
||||
return mCurrScroller.getCurrVelocity();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user