diff options
| author | Adam Powell <adamp@google.com> | 2010-01-25 15:10:44 -0800 |
|---|---|---|
| committer | Adam Powell <adamp@google.com> | 2010-02-05 17:57:41 -0800 |
| commit | 458034799861fef47e00d85d528b4dac5e00bd51 (patch) | |
| tree | 59ac7eea5cc95ea3b9fd5368e435a73f3d0a3d14 /core | |
| parent | 420bc12ed03dd0514e0d0400385ceba3e91bbe2c (diff) | |
| download | frameworks_base-458034799861fef47e00d85d528b4dac5e00bd51.zip frameworks_base-458034799861fef47e00d85d528b4dac5e00bd51.tar.gz frameworks_base-458034799861fef47e00d85d528b4dac5e00bd51.tar.bz2 | |
ListView updates. Fixed several overscrolling bugs. Added programmatic
scrolling to ListView. Added auto-scrolling to show expanded content
for ExpandableListView. Fixed an AbsListView recycler bug where
offscreen views would stick around in the view hierarchy.
Addresses bugs 1161597 and 1119429.
Diffstat (limited to 'core')
| -rw-r--r-- | core/java/android/widget/AbsListView.java | 462 | ||||
| -rw-r--r-- | core/java/android/widget/ExpandableListView.java | 12 |
2 files changed, 391 insertions, 83 deletions
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 66a7631..fd6af05 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -32,7 +32,6 @@ 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; @@ -307,6 +306,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * Handles one frame of a fling */ private FlingRunnable mFlingRunnable; + + /** + * Handles scrolling between positions within the list. + */ + private PositionScroller mPositionScroller; /** * The offset in pixels form the top of the AdapterView to the top @@ -1588,6 +1592,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // let the fling runnable report it's new state which // should be idle mFlingRunnable.endFling(); + if (mScrollY != 0) { + mScrollY = 0; + invalidate(); + } } // Always hide the type filter dismissPopup(); @@ -1935,9 +1943,13 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } else { int touchMode = mTouchMode; if (touchMode == TOUCH_MODE_OVERSCROLL || touchMode == TOUCH_MODE_OVERFLING) { - mScrollY = 0; if (mFlingRunnable != null) { mFlingRunnable.endFling(); + + if (mScrollY != 0) { + mScrollY = 0; + invalidate(); + } } } } @@ -2052,9 +2064,11 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te 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) { - trackMotionScroll(deltaY, incrementalDeltaY); + atEdge = trackMotionScroll(deltaY, incrementalDeltaY); } // Check to see if we have bumped into the scroll limit @@ -2064,7 +2078,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te // supposed to be final int motionViewRealTop = motionView.getTop(); final int motionViewNewTop = mMotionViewNewTop; - if (motionViewRealTop != motionViewNewTop) { + if (atEdge) { // Apply overscroll mScrollY -= incrementalDeltaY - (motionViewRealTop - motionViewPrevTop); @@ -2440,7 +2454,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } } - + void startSpringback() { if (mScroller.springback(0, mScrollY, 0, 0, 0, 0)) { mTouchMode = TOUCH_MODE_OVERFLING; @@ -2448,19 +2462,33 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te 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); } - + + void startScroll(int distance, int duration) { + int initialY = distance < 0 ? Integer.MAX_VALUE : 0; + mLastFlingY = initialY; + mScroller.startScroll(0, initialY, 0, distance, duration); + mTouchMode = TOUCH_MODE_FLING; + post(this); + } + private void endFling() { mTouchMode = TOUCH_MODE_REST; + reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); clearScrollingCache(); + removeCallbacks(this); + + if (mPositionScroller != null) { + removeCallbacks(mPositionScroller); + } } public void run() { @@ -2553,6 +2581,278 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te } } + + + class PositionScroller implements Runnable { + private static final int SCROLL_DURATION = 400; + + private static final int MOVE_DOWN_POS = 1; + private static final int MOVE_UP_POS = 2; + private static final int MOVE_DOWN_BOUND = 3; + private static final int MOVE_UP_BOUND = 4; + + private int mMode; + private int mTargetPos; + private int mBoundPos; + private int mLastSeenPos; + private int mScrollDuration; + private int mExtraScroll; + + PositionScroller() { + mExtraScroll = ViewConfiguration.get(mContext).getScaledFadingEdgeLength(); + } + + void start(int position) { + final int firstPos = mFirstPosition; + final int lastPos = firstPos + getChildCount() - 1; + + int viewTravelCount = 0; + if (position <= firstPos) { + viewTravelCount = firstPos - position + 1; + mMode = MOVE_UP_POS; + } else if (position >= lastPos) { + viewTravelCount = position - lastPos + 1; + mMode = MOVE_DOWN_POS; + } else { + // Already on screen, nothing to do + return; + } + + if (viewTravelCount > 0) { + mScrollDuration = SCROLL_DURATION / viewTravelCount; + } else { + mScrollDuration = SCROLL_DURATION; + } + mTargetPos = position; + mBoundPos = INVALID_POSITION; + mLastSeenPos = INVALID_POSITION; + + post(this); + } + + void start(int position, int boundPosition) { + if (boundPosition == INVALID_POSITION) { + start(position); + return; + } + + final int firstPos = mFirstPosition; + final int lastPos = firstPos + getChildCount() - 1; + + int viewTravelCount = 0; + if (position < firstPos) { + final int boundPosFromLast = lastPos - boundPosition; + if (boundPosFromLast < 1) { + // Moving would shift our bound position off the screen. Abort. + return; + } + + final int posTravel = firstPos - position + 1; + final int boundTravel = boundPosFromLast - 1; + if (boundTravel < posTravel) { + viewTravelCount = boundTravel; + mMode = MOVE_UP_BOUND; + } else { + viewTravelCount = posTravel; + mMode = MOVE_UP_POS; + } + } else if (position > lastPos) { + final int boundPosFromFirst = boundPosition - firstPos; + if (boundPosFromFirst < 1) { + // Moving would shift our bound position off the screen. Abort. + return; + } + + final int posTravel = position - lastPos + 1; + final int boundTravel = boundPosFromFirst - 1; + if (boundTravel < posTravel) { + viewTravelCount = boundTravel; + mMode = MOVE_DOWN_BOUND; + } else { + viewTravelCount = posTravel; + mMode = MOVE_DOWN_POS; + } + } else { + // Already on screen, nothing to do + return; + } + + if (viewTravelCount > 0) { + mScrollDuration = SCROLL_DURATION / viewTravelCount; + } else { + mScrollDuration = SCROLL_DURATION; + } + mTargetPos = position; + mBoundPos = boundPosition; + mLastSeenPos = INVALID_POSITION; + + post(this); + } + + void stop() { + removeCallbacks(this); + } + + public void run() { + final int listHeight = getHeight(); + final int firstPos = mFirstPosition; + + switch (mMode) { + case MOVE_DOWN_POS: { + final int lastViewIndex = getChildCount() - 1; + final int lastPos = firstPos + lastViewIndex; + + if (lastPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View lastView = getChildAt(lastViewIndex); + final int lastViewHeight = lastView.getHeight(); + final int lastViewTop = lastView.getTop(); + final int lastViewPixelsShowing = listHeight - lastViewTop; + + smoothScrollBy(lastViewHeight - lastViewPixelsShowing + mExtraScroll, + mScrollDuration); + + mLastSeenPos = lastPos; + if (lastPos != mTargetPos) { + post(this); + } + break; + } + + case MOVE_DOWN_BOUND: { + final int nextViewIndex = 1; + if (firstPos == mBoundPos || getChildCount() <= nextViewIndex) { + return; + } + final int nextPos = firstPos + nextViewIndex; + + if (nextPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View nextView = getChildAt(nextViewIndex); + final int nextViewHeight = nextView.getHeight(); + final int nextViewTop = nextView.getTop(); + final int extraScroll = mExtraScroll; + if (nextPos != mBoundPos) { + smoothScrollBy(Math.max(0, nextViewHeight + nextViewTop - extraScroll), + mScrollDuration); + + mLastSeenPos = nextPos; + + post(this); + } else { + if (nextViewTop > extraScroll) { + smoothScrollBy(nextViewTop - extraScroll, mScrollDuration); + } + } + break; + } + + case MOVE_UP_POS: { + if (firstPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View firstView = getChildAt(0); + final int firstViewTop = firstView.getTop(); + + smoothScrollBy(firstViewTop - mExtraScroll, mScrollDuration); + + mLastSeenPos = firstPos; + + if (firstPos != mTargetPos) { + post(this); + } + break; + } + + case MOVE_UP_BOUND: { + final int lastViewIndex = getChildCount() - 2; + if (lastViewIndex < 0) { + return; + } + final int lastPos = firstPos + lastViewIndex; + + if (lastPos == mLastSeenPos) { + // No new views, let things keep going. + post(this); + return; + } + + final View lastView = getChildAt(lastViewIndex); + final int lastViewHeight = lastView.getHeight(); + final int lastViewTop = lastView.getTop(); + final int lastViewPixelsShowing = listHeight - lastViewTop; + mLastSeenPos = lastPos; + if (lastPos != mBoundPos) { + smoothScrollBy(-(lastViewPixelsShowing - mExtraScroll), mScrollDuration); + post(this); + } else { + final int bottom = listHeight - mExtraScroll; + final int lastViewBottom = lastViewTop + lastViewHeight; + if (bottom > lastViewBottom) { + smoothScrollBy(-(bottom - lastViewBottom), mScrollDuration); + } + } + break; + } + + default: + break; + } + } + } + + /** + * Smoothly scroll to the specified adapter position. The view will + * scroll such that the indicated position is displayed. + * @param position Scroll to this adapter position. + */ + public void smoothScrollToPosition(int position) { + if (mPositionScroller == null) { + mPositionScroller = new PositionScroller(); + } + mPositionScroller.start(position); + } + + /** + * Smoothly scroll to the specified adapter position. The view will + * scroll such that the indicated position is displayed, but it will + * stop early if scrolling further would scroll boundPosition out of + * view. + * @param position Scroll to this adapter position. + * @param boundPosition Do not scroll if it would move this adapter + * position out of view. + */ + public void smoothScrollToPosition(int position, int boundPosition) { + if (mPositionScroller == null) { + mPositionScroller = new PositionScroller(); + } + mPositionScroller.start(position, boundPosition); + } + + /** + * Smoothly scroll by distance pixels over duration milliseconds. + * @param distance Distance to scroll in pixels. + * @param duration Duration of the scroll animation in milliseconds. + */ + public void smoothScrollBy(int distance, int duration) { + if (mFlingRunnable == null) { + mFlingRunnable = new FlingRunnable(); + } else { + mFlingRunnable.endFling(); + } + mFlingRunnable.startScroll(distance, duration); + } private void createScrollingCache() { if (mScrollingCacheEnabled && !mCachingStarted) { @@ -2588,11 +2888,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te * @param deltaY Amount to offset mMotionView. This is the accumulated delta since the motion * began. Positive numbers mean the user's finger is moving down the screen. * @param incrementalDeltaY Change in deltaY from the previous event. + * @return true if we're already at the beginning/end of the list and have nothing to do. */ - void trackMotionScroll(int deltaY, int incrementalDeltaY) { + boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { final int childCount = getChildCount(); if (childCount == 0) { - return; + return true; } final int firstTop = getChildAt(0).getTop(); @@ -2618,98 +2919,99 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); } - final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); - - if (spaceAbove >= absIncrementalDeltaY && spaceBelow >= absIncrementalDeltaY) { - hideSelector(); - offsetChildrenTopAndBottom(incrementalDeltaY); - if (!awakenScrollBars()) { - invalidate(); - } - mMotionViewNewTop = mMotionViewOriginalTop + deltaY; - } else { - final int firstPosition = mFirstPosition; + final int firstPosition = mFirstPosition; - if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) { - // Don't need to move views down if the top of the first position is already visible - return; - } + if (firstPosition == 0 && firstTop >= listPadding.top && deltaY > 0) { + // Don't need to move views down if the top of the first position + // is already visible + return true; + } - if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) { - // Don't need to move views up if the bottom of the last position is already visible - return; - } + if (firstPosition + childCount == mItemCount && lastBottom <= end && deltaY < 0) { + // Don't need to move views up if the bottom of the last position + // is already visible + return true; + } - final boolean down = incrementalDeltaY < 0; + final boolean down = incrementalDeltaY < 0; - hideSelector(); + hideSelector(); - final int headerViewsCount = getHeaderViewsCount(); - final int footerViewsStart = mItemCount - getFooterViewsCount(); + final int headerViewsCount = getHeaderViewsCount(); + final int footerViewsStart = mItemCount - getFooterViewsCount(); - int start = 0; - int count = 0; + int start = 0; + int count = 0; - if (down) { - final int top = listPadding.top - incrementalDeltaY; - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (child.getBottom() >= top) { - break; - } else { - count++; - int position = firstPosition + i; - if (position >= headerViewsCount && position < footerViewsStart) { - mRecycler.addScrapView(child); - - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, - ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, - firstPosition + i, -1); - } + if (down) { + final int top = listPadding.top - incrementalDeltaY; + for (int i = 0; i < childCount; i++) { + final View child = getChildAt(i); + if (child.getBottom() >= top) { + break; + } else { + count++; + int position = firstPosition + i; + if (position >= headerViewsCount && position < footerViewsStart) { + mRecycler.addScrapView(child); + + if (ViewDebug.TRACE_RECYCLER) { + ViewDebug.trace(child, + ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, + firstPosition + i, -1); } } } - } else { - final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY; - for (int i = childCount - 1; i >= 0; i--) { - final View child = getChildAt(i); - if (child.getTop() <= bottom) { - break; - } else { - start = i; - count++; - int position = firstPosition + i; - if (position >= headerViewsCount && position < footerViewsStart) { - mRecycler.addScrapView(child); - - if (ViewDebug.TRACE_RECYCLER) { - ViewDebug.trace(child, - ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, - firstPosition + i, -1); - } + } + } else { + final int bottom = getHeight() - listPadding.bottom - incrementalDeltaY; + for (int i = childCount - 1; i >= 0; i--) { + final View child = getChildAt(i); + if (child.getTop() <= bottom) { + break; + } else { + start = i; + count++; + int position = firstPosition + i; + if (position >= headerViewsCount && position < footerViewsStart) { + mRecycler.addScrapView(child); + + if (ViewDebug.TRACE_RECYCLER) { + ViewDebug.trace(child, + ViewDebug.RecyclerTraceType.MOVE_TO_SCRAP_HEAP, + firstPosition + i, -1); } } } } + } + + mMotionViewNewTop = mMotionViewOriginalTop + deltaY; - mMotionViewNewTop = mMotionViewOriginalTop + deltaY; + mBlockLayoutRequests = true; - mBlockLayoutRequests = true; + if (count > 0) { detachViewsFromParent(start, count); - offsetChildrenTopAndBottom(incrementalDeltaY); + } + offsetChildrenTopAndBottom(incrementalDeltaY); - if (down) { - mFirstPosition += count; - } + if (down) { + mFirstPosition += count; + } - invalidate(); - fillGap(down); - mBlockLayoutRequests = false; + invalidate(); - invokeOnItemScrollListener(); - awakenScrollBars(); + final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); + if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { + fillGap(down); } + + mBlockLayoutRequests = false; + + invokeOnItemScrollListener(); + awakenScrollBars(); + + return false; } /** diff --git a/core/java/android/widget/ExpandableListView.java b/core/java/android/widget/ExpandableListView.java index 405461a..a4b20da 100644 --- a/core/java/android/widget/ExpandableListView.java +++ b/core/java/android/widget/ExpandableListView.java @@ -18,14 +18,12 @@ package android.widget; import com.android.internal.R; -import java.util.ArrayList; - import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Rect; -import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.util.AttributeSet; @@ -35,6 +33,8 @@ import android.view.View; import android.view.ContextMenu.ContextMenuInfo; import android.widget.ExpandableListConnector.PositionMetadata; +import java.util.ArrayList; + /** * A view that shows items in a vertically scrolling two-level list. This * differs from the {@link ListView} by allowing two levels: groups which can @@ -541,6 +541,12 @@ public class ExpandableListView extends ListView { if (mOnGroupExpandListener != null) { mOnGroupExpandListener.onGroupExpand(posMetadata.position.groupPos); } + + final int groupPos = posMetadata.position.groupPos; + final int groupFlatPos = posMetadata.position.flatListPos; + + smoothScrollToPosition(groupFlatPos + mAdapter.getChildrenCount(groupPos), + groupFlatPos); } returnValue = true; |
