2929import android .os .SystemClock ;
3030import android .view .MotionEvent ;
3131import android .view .View ;
32+ import android .view .ViewConfiguration ;
3233import android .widget .AbsListView .OnScrollListener ;
3334
3435/**
3536 * Helper class for AbsListView to draw and control the Fast Scroll thumb
3637 */
3738class FastScroller {
39+ private static final String TAG = "FastScroller" ;
3840
3941 // Minimum number of pages to justify showing a fast scroll thumb
4042 private static int MIN_PAGES = 4 ;
@@ -81,15 +83,15 @@ class FastScroller {
8183 private Drawable mOverlayDrawableLeft ;
8284 private Drawable mOverlayDrawableRight ;
8385
84- private int mThumbH ;
85- private int mThumbW ;
86- private int mThumbY ;
86+ int mThumbH ;
87+ int mThumbW ;
88+ int mThumbY ;
8789
8890 private RectF mOverlayPos ;
8991 private int mOverlaySize ;
9092
91- private AbsListView mList ;
92- private boolean mScrollCompleted ;
93+ AbsListView mList ;
94+ boolean mScrollCompleted ;
9395 private int mVisibleItem ;
9496 private Paint mPaint ;
9597 private int mListOffset ;
@@ -105,7 +107,7 @@ class FastScroller {
105107
106108 private Handler mHandler = new Handler ();
107109
108- private BaseAdapter mListAdapter ;
110+ BaseAdapter mListAdapter ;
109111 private SectionIndexer mSectionIndexer ;
110112
111113 private boolean mChangedBounds ;
@@ -118,10 +120,36 @@ class FastScroller {
118120
119121 private boolean mMatchDragPosition ;
120122
123+ float mInitialTouchY ;
124+ boolean mPendingDrag ;
125+ private int mScaledTouchSlop ;
126+
121127 private static final int FADE_TIMEOUT = 1500 ;
128+ private static final int PENDING_DRAG_DELAY = 180 ;
122129
123130 private final Rect mTmpRect = new Rect ();
124131
132+ private final Runnable mDeferStartDrag = new Runnable () {
133+ public void run () {
134+ if (mList .mIsAttached ) {
135+ beginDrag ();
136+
137+ final int viewHeight = mList .getHeight ();
138+ // Jitter
139+ int newThumbY = (int ) mInitialTouchY - mThumbH + 10 ;
140+ if (newThumbY < 0 ) {
141+ newThumbY = 0 ;
142+ } else if (newThumbY + mThumbH > viewHeight ) {
143+ newThumbY = viewHeight - mThumbH ;
144+ }
145+ mThumbY = newThumbY ;
146+ scrollTo ((float ) mThumbY / (viewHeight - mThumbH ));
147+ }
148+
149+ mPendingDrag = false ;
150+ }
151+ };
152+
125153 public FastScroller (Context context , AbsListView listView ) {
126154 mList = listView ;
127155 init (context );
@@ -264,6 +292,8 @@ private void init(Context context) {
264292
265293 ta .recycle ();
266294
295+ mScaledTouchSlop = ViewConfiguration .get (context ).getScaledTouchSlop ();
296+
267297 mMatchDragPosition = context .getApplicationInfo ().targetSdkVersion >=
268298 android .os .Build .VERSION_CODES .HONEYCOMB ;
269299
@@ -456,7 +486,7 @@ Object[] getSections() {
456486 return mSections ;
457487 }
458488
459- private void getSectionsFromIndexer () {
489+ void getSectionsFromIndexer () {
460490 Adapter adapter = mList .getAdapter ();
461491 mSectionIndexer = null ;
462492 if (adapter instanceof HeaderViewListAdapter ) {
@@ -489,7 +519,7 @@ public void onSectionsChanged() {
489519 mListAdapter = null ;
490520 }
491521
492- private void scrollTo (float position ) {
522+ void scrollTo (float position ) {
493523 int count = mList .getCount ();
494524 mScrollCompleted = false ;
495525 float fThreshold = (1.0f / count ) / 8 ;
@@ -647,12 +677,45 @@ private void cancelFling() {
647677 cancelFling .recycle ();
648678 }
649679
680+ void cancelPendingDrag () {
681+ mList .removeCallbacks (mDeferStartDrag );
682+ mPendingDrag = false ;
683+ }
684+
685+ void startPendingDrag () {
686+ mPendingDrag = true ;
687+ mList .postDelayed (mDeferStartDrag , PENDING_DRAG_DELAY );
688+ }
689+
690+ void beginDrag () {
691+ setState (STATE_DRAGGING );
692+ if (mListAdapter == null && mList != null ) {
693+ getSectionsFromIndexer ();
694+ }
695+ if (mList != null ) {
696+ mList .requestDisallowInterceptTouchEvent (true );
697+ mList .reportScrollStateChange (OnScrollListener .SCROLL_STATE_TOUCH_SCROLL );
698+ }
699+
700+ cancelFling ();
701+ }
702+
650703 boolean onInterceptTouchEvent (MotionEvent ev ) {
651- if (mState > STATE_NONE && ev .getAction () == MotionEvent .ACTION_DOWN ) {
652- if (isPointInside (ev .getX (), ev .getY ())) {
653- setState (STATE_DRAGGING );
654- return true ;
655- }
704+ switch (ev .getActionMasked ()) {
705+ case MotionEvent .ACTION_DOWN :
706+ if (mState > STATE_NONE && isPointInside (ev .getX (), ev .getY ())) {
707+ if (!mList .isInScrollingContainer ()) {
708+ beginDrag ();
709+ return true ;
710+ }
711+ mInitialTouchY = ev .getY ();
712+ startPendingDrag ();
713+ }
714+ break ;
715+ case MotionEvent .ACTION_UP :
716+ case MotionEvent .ACTION_CANCEL :
717+ cancelPendingDrag ();
718+ break ;
656719 }
657720 return false ;
658721 }
@@ -666,19 +729,32 @@ boolean onTouchEvent(MotionEvent me) {
666729
667730 if (action == MotionEvent .ACTION_DOWN ) {
668731 if (isPointInside (me .getX (), me .getY ())) {
669- setState ( STATE_DRAGGING );
670- if ( mListAdapter == null && mList != null ) {
671- getSectionsFromIndexer () ;
732+ if (! mList . isInScrollingContainer ()) {
733+ beginDrag ();
734+ return true ;
672735 }
673- if (mList != null ) {
674- mList .requestDisallowInterceptTouchEvent (true );
675- mList .reportScrollStateChange (OnScrollListener .SCROLL_STATE_TOUCH_SCROLL );
736+ mInitialTouchY = me .getY ();
737+ startPendingDrag ();
738+ }
739+ } else if (action == MotionEvent .ACTION_UP ) { // don't add ACTION_CANCEL here
740+ if (mPendingDrag ) {
741+ // Allow a tap to scroll.
742+ beginDrag ();
743+
744+ final int viewHeight = mList .getHeight ();
745+ // Jitter
746+ int newThumbY = (int ) me .getY () - mThumbH + 10 ;
747+ if (newThumbY < 0 ) {
748+ newThumbY = 0 ;
749+ } else if (newThumbY + mThumbH > viewHeight ) {
750+ newThumbY = viewHeight - mThumbH ;
676751 }
752+ mThumbY = newThumbY ;
753+ scrollTo ((float ) mThumbY / (viewHeight - mThumbH ));
677754
678- cancelFling ();
679- return true ;
755+ cancelPendingDrag ();
756+ // Will hit the STATE_DRAGGING check below
680757 }
681- } else if (action == MotionEvent .ACTION_UP ) { // don't add ACTION_CANCEL here
682758 if (mState == STATE_DRAGGING ) {
683759 if (mList != null ) {
684760 // ViewGroup does the right thing already, but there might
@@ -698,6 +774,23 @@ boolean onTouchEvent(MotionEvent me) {
698774 return true ;
699775 }
700776 } else if (action == MotionEvent .ACTION_MOVE ) {
777+ if (mPendingDrag ) {
778+ final float y = me .getY ();
779+ if (Math .abs (y - mInitialTouchY ) > mScaledTouchSlop ) {
780+ setState (STATE_DRAGGING );
781+ if (mListAdapter == null && mList != null ) {
782+ getSectionsFromIndexer ();
783+ }
784+ if (mList != null ) {
785+ mList .requestDisallowInterceptTouchEvent (true );
786+ mList .reportScrollStateChange (OnScrollListener .SCROLL_STATE_TOUCH_SCROLL );
787+ }
788+
789+ cancelFling ();
790+ cancelPendingDrag ();
791+ // Will hit the STATE_DRAGGING check below
792+ }
793+ }
701794 if (mState == STATE_DRAGGING ) {
702795 final int viewHeight = mList .getHeight ();
703796 // Jitter
@@ -717,6 +810,8 @@ boolean onTouchEvent(MotionEvent me) {
717810 }
718811 return true ;
719812 }
813+ } else if (action == MotionEvent .ACTION_CANCEL ) {
814+ cancelPendingDrag ();
720815 }
721816 return false ;
722817 }
0 commit comments