FastScroller.java revision b1f498afc379ec84b86137e91afda6eccb8f4c0a
1/* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.widget; 18 19import android.content.Context; 20import android.content.res.ColorStateList; 21import android.content.res.TypedArray; 22import android.graphics.Canvas; 23import android.graphics.Paint; 24import android.graphics.Rect; 25import android.graphics.RectF; 26import android.graphics.drawable.Drawable; 27import android.graphics.drawable.NinePatchDrawable; 28import android.os.Handler; 29import android.os.SystemClock; 30import android.view.MotionEvent; 31import android.view.View; 32import android.widget.AbsListView.OnScrollListener; 33 34/** 35 * Helper class for AbsListView to draw and control the Fast Scroll thumb 36 */ 37class FastScroller { 38 39 // Minimum number of pages to justify showing a fast scroll thumb 40 private static int MIN_PAGES = 4; 41 // Scroll thumb not showing 42 private static final int STATE_NONE = 0; 43 // Not implemented yet - fade-in transition 44 private static final int STATE_ENTER = 1; 45 // Scroll thumb visible and moving along with the scrollbar 46 private static final int STATE_VISIBLE = 2; 47 // Scroll thumb being dragged by user 48 private static final int STATE_DRAGGING = 3; 49 // Scroll thumb fading out due to inactivity timeout 50 private static final int STATE_EXIT = 4; 51 52 private static final int[] PRESSED_STATES = new int[] { 53 android.R.attr.state_pressed 54 }; 55 56 private static final int[] DEFAULT_STATES = new int[0]; 57 58 private static final int[] ATTRS = new int[] { 59 android.R.attr.fastScrollTextColor, 60 android.R.attr.fastScrollThumbDrawable, 61 android.R.attr.fastScrollTrackDrawable, 62 android.R.attr.fastScrollPreviewBackgroundLeft, 63 android.R.attr.fastScrollPreviewBackgroundRight, 64 android.R.attr.fastScrollOverlayPosition 65 }; 66 67 private static final int TEXT_COLOR = 0; 68 private static final int THUMB_DRAWABLE = 1; 69 private static final int TRACK_DRAWABLE = 2; 70 private static final int PREVIEW_BACKGROUND_LEFT = 3; 71 private static final int PREVIEW_BACKGROUND_RIGHT = 4; 72 private static final int OVERLAY_POSITION = 5; 73 74 private static final int OVERLAY_FLOATING = 0; 75 private static final int OVERLAY_AT_THUMB = 1; 76 77 private Drawable mThumbDrawable; 78 private Drawable mOverlayDrawable; 79 private Drawable mTrackDrawable; 80 81 private Drawable mOverlayDrawableLeft; 82 private Drawable mOverlayDrawableRight; 83 84 private int mThumbH; 85 private int mThumbW; 86 private int mThumbY; 87 88 private RectF mOverlayPos; 89 private int mOverlaySize; 90 91 private AbsListView mList; 92 private boolean mScrollCompleted; 93 private int mVisibleItem; 94 private Paint mPaint; 95 private int mListOffset; 96 private int mItemCount = -1; 97 private boolean mLongList; 98 99 private Object [] mSections; 100 private String mSectionText; 101 private boolean mDrawOverlay; 102 private ScrollFade mScrollFade; 103 104 private int mState; 105 106 private Handler mHandler = new Handler(); 107 108 private BaseAdapter mListAdapter; 109 private SectionIndexer mSectionIndexer; 110 111 private boolean mChangedBounds; 112 113 private int mPosition; 114 115 private boolean mAlwaysShow; 116 117 private int mOverlayPosition; 118 119 private static final int FADE_TIMEOUT = 1500; 120 121 private final Rect mTmpRect = new Rect(); 122 123 public FastScroller(Context context, AbsListView listView) { 124 mList = listView; 125 init(context); 126 } 127 128 public void setAlwaysShow(boolean alwaysShow) { 129 mAlwaysShow = alwaysShow; 130 if (alwaysShow) { 131 mHandler.removeCallbacks(mScrollFade); 132 setState(STATE_VISIBLE); 133 } else if (mState == STATE_VISIBLE) { 134 mHandler.postDelayed(mScrollFade, FADE_TIMEOUT); 135 } 136 } 137 138 public boolean isAlwaysShowEnabled() { 139 return mAlwaysShow; 140 } 141 142 private void refreshDrawableState() { 143 int[] state = mState == STATE_DRAGGING ? PRESSED_STATES : DEFAULT_STATES; 144 145 if (mThumbDrawable != null && mThumbDrawable.isStateful()) { 146 mThumbDrawable.setState(state); 147 } 148 if (mTrackDrawable != null && mTrackDrawable.isStateful()) { 149 mTrackDrawable.setState(state); 150 } 151 } 152 153 public void setScrollbarPosition(int position) { 154 mPosition = position; 155 switch (position) { 156 default: 157 case View.SCROLLBAR_POSITION_DEFAULT: 158 case View.SCROLLBAR_POSITION_RIGHT: 159 mOverlayDrawable = mOverlayDrawableRight; 160 break; 161 case View.SCROLLBAR_POSITION_LEFT: 162 mOverlayDrawable = mOverlayDrawableLeft; 163 break; 164 } 165 } 166 167 public int getWidth() { 168 return mThumbW; 169 } 170 171 public void setState(int state) { 172 switch (state) { 173 case STATE_NONE: 174 mHandler.removeCallbacks(mScrollFade); 175 mList.invalidate(); 176 break; 177 case STATE_VISIBLE: 178 if (mState != STATE_VISIBLE) { // Optimization 179 resetThumbPos(); 180 } 181 // Fall through 182 case STATE_DRAGGING: 183 mHandler.removeCallbacks(mScrollFade); 184 break; 185 case STATE_EXIT: 186 int viewWidth = mList.getWidth(); 187 mList.invalidate(viewWidth - mThumbW, mThumbY, viewWidth, mThumbY + mThumbH); 188 break; 189 } 190 mState = state; 191 refreshDrawableState(); 192 } 193 194 public int getState() { 195 return mState; 196 } 197 198 private void resetThumbPos() { 199 final int viewWidth = mList.getWidth(); 200 // Bounds are always top right. Y coordinate get's translated during draw 201 switch (mPosition) { 202 case View.SCROLLBAR_POSITION_DEFAULT: 203 case View.SCROLLBAR_POSITION_RIGHT: 204 mThumbDrawable.setBounds(viewWidth - mThumbW, 0, viewWidth, mThumbH); 205 break; 206 case View.SCROLLBAR_POSITION_LEFT: 207 mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH); 208 break; 209 } 210 mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX); 211 } 212 213 private void useThumbDrawable(Context context, Drawable drawable) { 214 mThumbDrawable = drawable; 215 if (drawable instanceof NinePatchDrawable) { 216 mThumbW = context.getResources().getDimensionPixelSize( 217 com.android.internal.R.dimen.fastscroll_thumb_width); 218 mThumbH = context.getResources().getDimensionPixelSize( 219 com.android.internal.R.dimen.fastscroll_thumb_height); 220 } else { 221 mThumbW = drawable.getIntrinsicWidth(); 222 mThumbH = drawable.getIntrinsicHeight(); 223 } 224 mChangedBounds = true; 225 } 226 227 private void init(Context context) { 228 // Get both the scrollbar states drawables 229 TypedArray ta = context.getTheme().obtainStyledAttributes(ATTRS); 230 useThumbDrawable(context, ta.getDrawable(THUMB_DRAWABLE)); 231 mTrackDrawable = ta.getDrawable(TRACK_DRAWABLE); 232 233 mOverlayDrawableLeft = ta.getDrawable(PREVIEW_BACKGROUND_LEFT); 234 mOverlayDrawableRight = ta.getDrawable(PREVIEW_BACKGROUND_RIGHT); 235 mOverlayPosition = ta.getInt(OVERLAY_POSITION, OVERLAY_FLOATING); 236 237 mScrollCompleted = true; 238 239 getSectionsFromIndexer(); 240 241 mOverlaySize = context.getResources().getDimensionPixelSize( 242 com.android.internal.R.dimen.fastscroll_overlay_size); 243 mOverlayPos = new RectF(); 244 mScrollFade = new ScrollFade(); 245 mPaint = new Paint(); 246 mPaint.setAntiAlias(true); 247 mPaint.setTextAlign(Paint.Align.CENTER); 248 mPaint.setTextSize(mOverlaySize / 2); 249 250 ColorStateList textColor = ta.getColorStateList(TEXT_COLOR); 251 int textColorNormal = textColor.getDefaultColor(); 252 mPaint.setColor(textColorNormal); 253 mPaint.setStyle(Paint.Style.FILL_AND_STROKE); 254 255 // to show mOverlayDrawable properly 256 if (mList.getWidth() > 0 && mList.getHeight() > 0) { 257 onSizeChanged(mList.getWidth(), mList.getHeight(), 0, 0); 258 } 259 260 mState = STATE_NONE; 261 refreshDrawableState(); 262 263 ta.recycle(); 264 265 setScrollbarPosition(mList.getVerticalScrollbarPosition()); 266 } 267 268 void stop() { 269 setState(STATE_NONE); 270 } 271 272 boolean isVisible() { 273 return !(mState == STATE_NONE); 274 } 275 276 public void draw(Canvas canvas) { 277 278 if (mState == STATE_NONE) { 279 // No need to draw anything 280 return; 281 } 282 283 final int y = mThumbY; 284 final int viewWidth = mList.getWidth(); 285 final FastScroller.ScrollFade scrollFade = mScrollFade; 286 287 int alpha = -1; 288 if (mState == STATE_EXIT) { 289 alpha = scrollFade.getAlpha(); 290 if (alpha < ScrollFade.ALPHA_MAX / 2) { 291 mThumbDrawable.setAlpha(alpha * 2); 292 } 293 int left = 0; 294 switch (mPosition) { 295 case View.SCROLLBAR_POSITION_DEFAULT: 296 case View.SCROLLBAR_POSITION_RIGHT: 297 left = viewWidth - (mThumbW * alpha) / ScrollFade.ALPHA_MAX; 298 break; 299 case View.SCROLLBAR_POSITION_LEFT: 300 left = -mThumbW + (mThumbW * alpha) / ScrollFade.ALPHA_MAX; 301 break; 302 } 303 mThumbDrawable.setBounds(left, 0, left + mThumbW, mThumbH); 304 mChangedBounds = true; 305 } 306 307 if (mTrackDrawable != null) { 308 final Rect thumbBounds = mThumbDrawable.getBounds(); 309 final int left = thumbBounds.left; 310 final int halfThumbHeight = (thumbBounds.bottom - thumbBounds.top) / 2; 311 final int trackWidth = mTrackDrawable.getIntrinsicWidth(); 312 final int trackLeft = (left + mThumbW / 2) - trackWidth / 2; 313 mTrackDrawable.setBounds(trackLeft, halfThumbHeight, 314 trackLeft + trackWidth, mList.getHeight() - halfThumbHeight); 315 mTrackDrawable.draw(canvas); 316 } 317 318 canvas.translate(0, y); 319 mThumbDrawable.draw(canvas); 320 canvas.translate(0, -y); 321 322 // If user is dragging the scroll bar, draw the alphabet overlay 323 if (mState == STATE_DRAGGING && mDrawOverlay) { 324 if (mOverlayPosition == OVERLAY_AT_THUMB) { 325 int left = 0; 326 switch (mPosition) { 327 default: 328 case View.SCROLLBAR_POSITION_DEFAULT: 329 case View.SCROLLBAR_POSITION_RIGHT: 330 left = Math.max(0, 331 mThumbDrawable.getBounds().left - mThumbW - mOverlaySize); 332 break; 333 case View.SCROLLBAR_POSITION_LEFT: 334 left = Math.min(mThumbDrawable.getBounds().right + mThumbW, 335 mList.getWidth() - mOverlaySize); 336 break; 337 } 338 339 int top = Math.max(0, 340 Math.min(y + (mThumbH - mOverlaySize) / 2, mList.getHeight() - mOverlaySize)); 341 342 final RectF pos = mOverlayPos; 343 pos.left = left; 344 pos.right = pos.left + mOverlaySize; 345 pos.top = top; 346 pos.bottom = pos.top + mOverlaySize; 347 if (mOverlayDrawable != null) { 348 mOverlayDrawable.setBounds((int) pos.left, (int) pos.top, 349 (int) pos.right, (int) pos.bottom); 350 } 351 } 352 mOverlayDrawable.draw(canvas); 353 final Paint paint = mPaint; 354 float descent = paint.descent(); 355 final RectF rectF = mOverlayPos; 356 final Rect tmpRect = mTmpRect; 357 mOverlayDrawable.getPadding(tmpRect); 358 final int hOff = (tmpRect.right - tmpRect.left) / 2; 359 final int vOff = (tmpRect.bottom - tmpRect.top) / 2; 360 canvas.drawText(mSectionText, (int) (rectF.left + rectF.right) / 2 - hOff, 361 (int) (rectF.bottom + rectF.top) / 2 + mOverlaySize / 4 - descent - vOff, 362 paint); 363 } else if (mState == STATE_EXIT) { 364 if (alpha == 0) { // Done with exit 365 setState(STATE_NONE); 366 } else { 367 mList.invalidate(viewWidth - mThumbW, y, viewWidth, y + mThumbH); 368 } 369 } 370 } 371 372 void onSizeChanged(int w, int h, int oldw, int oldh) { 373 if (mThumbDrawable != null) { 374 switch (mPosition) { 375 default: 376 case View.SCROLLBAR_POSITION_DEFAULT: 377 case View.SCROLLBAR_POSITION_RIGHT: 378 mThumbDrawable.setBounds(w - mThumbW, 0, w, mThumbH); 379 break; 380 case View.SCROLLBAR_POSITION_LEFT: 381 mThumbDrawable.setBounds(0, 0, mThumbW, mThumbH); 382 break; 383 } 384 } 385 if (mOverlayPosition == OVERLAY_FLOATING) { 386 final RectF pos = mOverlayPos; 387 pos.left = (w - mOverlaySize) / 2; 388 pos.right = pos.left + mOverlaySize; 389 pos.top = h / 10; // 10% from top 390 pos.bottom = pos.top + mOverlaySize; 391 if (mOverlayDrawable != null) { 392 mOverlayDrawable.setBounds((int) pos.left, (int) pos.top, 393 (int) pos.right, (int) pos.bottom); 394 } 395 } 396 } 397 398 void onItemCountChanged(int oldCount, int newCount) { 399 if (mAlwaysShow) { 400 mLongList = true; 401 } 402 } 403 404 void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 405 int totalItemCount) { 406 // Are there enough pages to require fast scroll? Recompute only if total count changes 407 if (mItemCount != totalItemCount && visibleItemCount > 0) { 408 mItemCount = totalItemCount; 409 mLongList = mItemCount / visibleItemCount >= MIN_PAGES; 410 } 411 if (mAlwaysShow) { 412 mLongList = true; 413 } 414 if (!mLongList) { 415 if (mState != STATE_NONE) { 416 setState(STATE_NONE); 417 } 418 return; 419 } 420 if (totalItemCount - visibleItemCount > 0 && mState != STATE_DRAGGING ) { 421 mThumbY = getThumbPositionForListPosition(firstVisibleItem, visibleItemCount, 422 totalItemCount); 423 if (mChangedBounds) { 424 resetThumbPos(); 425 mChangedBounds = false; 426 } 427 } 428 mScrollCompleted = true; 429 if (firstVisibleItem == mVisibleItem) { 430 return; 431 } 432 mVisibleItem = firstVisibleItem; 433 if (mState != STATE_DRAGGING) { 434 setState(STATE_VISIBLE); 435 if (!mAlwaysShow) { 436 mHandler.postDelayed(mScrollFade, FADE_TIMEOUT); 437 } 438 } 439 } 440 441 SectionIndexer getSectionIndexer() { 442 return mSectionIndexer; 443 } 444 445 Object[] getSections() { 446 if (mListAdapter == null && mList != null) { 447 getSectionsFromIndexer(); 448 } 449 return mSections; 450 } 451 452 private void getSectionsFromIndexer() { 453 Adapter adapter = mList.getAdapter(); 454 mSectionIndexer = null; 455 if (adapter instanceof HeaderViewListAdapter) { 456 mListOffset = ((HeaderViewListAdapter)adapter).getHeadersCount(); 457 adapter = ((HeaderViewListAdapter)adapter).getWrappedAdapter(); 458 } 459 if (adapter instanceof ExpandableListConnector) { 460 ExpandableListAdapter expAdapter = ((ExpandableListConnector)adapter).getAdapter(); 461 if (expAdapter instanceof SectionIndexer) { 462 mSectionIndexer = (SectionIndexer) expAdapter; 463 mListAdapter = (BaseAdapter) adapter; 464 mSections = mSectionIndexer.getSections(); 465 } 466 } else { 467 if (adapter instanceof SectionIndexer) { 468 mListAdapter = (BaseAdapter) adapter; 469 mSectionIndexer = (SectionIndexer) adapter; 470 mSections = mSectionIndexer.getSections(); 471 472 } else { 473 mListAdapter = (BaseAdapter) adapter; 474 mSections = new String[] { " " }; 475 } 476 } 477 } 478 479 public void onSectionsChanged() { 480 mListAdapter = null; 481 } 482 483 private void scrollTo(float position) { 484 int count = mList.getCount(); 485 mScrollCompleted = false; 486 float fThreshold = (1.0f / count) / 8; 487 final Object[] sections = mSections; 488 int sectionIndex; 489 if (sections != null && sections.length > 1) { 490 final int nSections = sections.length; 491 int section = (int) (position * nSections); 492 if (section >= nSections) { 493 section = nSections - 1; 494 } 495 int exactSection = section; 496 sectionIndex = section; 497 int index = mSectionIndexer.getPositionForSection(section); 498 // Given the expected section and index, the following code will 499 // try to account for missing sections (no names starting with..) 500 // It will compute the scroll space of surrounding empty sections 501 // and interpolate the currently visible letter's range across the 502 // available space, so that there is always some list movement while 503 // the user moves the thumb. 504 int nextIndex = count; 505 int prevIndex = index; 506 int prevSection = section; 507 int nextSection = section + 1; 508 // Assume the next section is unique 509 if (section < nSections - 1) { 510 nextIndex = mSectionIndexer.getPositionForSection(section + 1); 511 } 512 513 // Find the previous index if we're slicing the previous section 514 if (nextIndex == index) { 515 // Non-existent letter 516 while (section > 0) { 517 section--; 518 prevIndex = mSectionIndexer.getPositionForSection(section); 519 if (prevIndex != index) { 520 prevSection = section; 521 sectionIndex = section; 522 break; 523 } else if (section == 0) { 524 // When section reaches 0 here, sectionIndex must follow it. 525 // Assuming mSectionIndexer.getPositionForSection(0) == 0. 526 sectionIndex = 0; 527 break; 528 } 529 } 530 } 531 // Find the next index, in case the assumed next index is not 532 // unique. For instance, if there is no P, then request for P's 533 // position actually returns Q's. So we need to look ahead to make 534 // sure that there is really a Q at Q's position. If not, move 535 // further down... 536 int nextNextSection = nextSection + 1; 537 while (nextNextSection < nSections && 538 mSectionIndexer.getPositionForSection(nextNextSection) == nextIndex) { 539 nextNextSection++; 540 nextSection++; 541 } 542 // Compute the beginning and ending scroll range percentage of the 543 // currently visible letter. This could be equal to or greater than 544 // (1 / nSections). 545 float fPrev = (float) prevSection / nSections; 546 float fNext = (float) nextSection / nSections; 547 if (prevSection == exactSection && position - fPrev < fThreshold) { 548 index = prevIndex; 549 } else { 550 index = prevIndex + (int) ((nextIndex - prevIndex) * (position - fPrev) 551 / (fNext - fPrev)); 552 } 553 // Don't overflow 554 if (index > count - 1) index = count - 1; 555 556 if (mList instanceof ExpandableListView) { 557 ExpandableListView expList = (ExpandableListView) mList; 558 expList.setSelectionFromTop(expList.getFlatListPosition( 559 ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0); 560 } else if (mList instanceof ListView) { 561 ((ListView)mList).setSelectionFromTop(index + mListOffset, 0); 562 } else { 563 mList.setSelection(index + mListOffset); 564 } 565 } else { 566 int index = (int) (position * count); 567 if (mList instanceof ExpandableListView) { 568 ExpandableListView expList = (ExpandableListView) mList; 569 expList.setSelectionFromTop(expList.getFlatListPosition( 570 ExpandableListView.getPackedPositionForGroup(index + mListOffset)), 0); 571 } else if (mList instanceof ListView) { 572 ((ListView)mList).setSelectionFromTop(index + mListOffset, 0); 573 } else { 574 mList.setSelection(index + mListOffset); 575 } 576 sectionIndex = -1; 577 } 578 579 if (sectionIndex >= 0) { 580 String text = mSectionText = sections[sectionIndex].toString(); 581 mDrawOverlay = (text.length() != 1 || text.charAt(0) != ' ') && 582 sectionIndex < sections.length; 583 } else { 584 mDrawOverlay = false; 585 } 586 } 587 588 private int getThumbPositionForListPosition(int firstVisibleItem, int visibleItemCount, 589 int totalItemCount) { 590 if (mSectionIndexer == null) { 591 getSectionsFromIndexer(); 592 } 593 if (mSectionIndexer == null) { 594 return ((mList.getHeight() - mThumbH) * firstVisibleItem) 595 / (totalItemCount - visibleItemCount); 596 } 597 598 firstVisibleItem -= mListOffset; 599 if (firstVisibleItem < 0) { 600 return 0; 601 } 602 totalItemCount -= mListOffset; 603 604 final int trackHeight = mList.getHeight() - mThumbH; 605 606 final int section = mSectionIndexer.getSectionForPosition(firstVisibleItem); 607 final int sectionPos = mSectionIndexer.getPositionForSection(section); 608 final int nextSectionPos = mSectionIndexer.getPositionForSection(section + 1); 609 final int sectionCount = mSectionIndexer.getSections().length; 610 final int positionsInSection = nextSectionPos - sectionPos; 611 612 final View child = mList.getChildAt(0); 613 final float incrementalPos = child == null ? 0 : firstVisibleItem + 614 (float) (mList.getPaddingTop() - child.getTop()) / child.getHeight(); 615 final float posWithinSection = (incrementalPos - sectionPos) / positionsInSection; 616 int result = (int) ((section + posWithinSection) / sectionCount * trackHeight); 617 618 // Fake out the scrollbar for the last item. Since the section indexer won't 619 // ever actually move the list in this end space, make scrolling across the last item 620 // account for whatever space is remaining. 621 if (firstVisibleItem > 0 && firstVisibleItem + visibleItemCount == totalItemCount) { 622 final View lastChild = mList.getChildAt(visibleItemCount - 1); 623 final float lastItemVisible = (float) (mList.getHeight() - mList.getPaddingBottom() 624 - lastChild.getTop()) / lastChild.getHeight(); 625 result += (trackHeight - result) * lastItemVisible; 626 } 627 628 return result; 629 } 630 631 private void cancelFling() { 632 // Cancel the list fling 633 MotionEvent cancelFling = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0); 634 mList.onTouchEvent(cancelFling); 635 cancelFling.recycle(); 636 } 637 638 boolean onInterceptTouchEvent(MotionEvent ev) { 639 if (mState > STATE_NONE && ev.getAction() == MotionEvent.ACTION_DOWN) { 640 if (isPointInside(ev.getX(), ev.getY())) { 641 setState(STATE_DRAGGING); 642 return true; 643 } 644 } 645 return false; 646 } 647 648 boolean onTouchEvent(MotionEvent me) { 649 if (mState == STATE_NONE) { 650 return false; 651 } 652 653 final int action = me.getAction(); 654 655 if (action == MotionEvent.ACTION_DOWN) { 656 if (isPointInside(me.getX(), me.getY())) { 657 setState(STATE_DRAGGING); 658 if (mListAdapter == null && mList != null) { 659 getSectionsFromIndexer(); 660 } 661 if (mList != null) { 662 mList.requestDisallowInterceptTouchEvent(true); 663 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 664 } 665 666 cancelFling(); 667 return true; 668 } 669 } else if (action == MotionEvent.ACTION_UP) { // don't add ACTION_CANCEL here 670 if (mState == STATE_DRAGGING) { 671 if (mList != null) { 672 // ViewGroup does the right thing already, but there might 673 // be other classes that don't properly reset on touch-up, 674 // so do this explicitly just in case. 675 mList.requestDisallowInterceptTouchEvent(false); 676 mList.reportScrollStateChange(OnScrollListener.SCROLL_STATE_IDLE); 677 } 678 setState(STATE_VISIBLE); 679 final Handler handler = mHandler; 680 handler.removeCallbacks(mScrollFade); 681 if (!mAlwaysShow) { 682 handler.postDelayed(mScrollFade, 1000); 683 } 684 685 mList.invalidate(); 686 return true; 687 } 688 } else if (action == MotionEvent.ACTION_MOVE) { 689 if (mState == STATE_DRAGGING) { 690 final int viewHeight = mList.getHeight(); 691 // Jitter 692 int newThumbY = (int) me.getY() - mThumbH + 10; 693 if (newThumbY < 0) { 694 newThumbY = 0; 695 } else if (newThumbY + mThumbH > viewHeight) { 696 newThumbY = viewHeight - mThumbH; 697 } 698 if (Math.abs(mThumbY - newThumbY) < 2) { 699 return true; 700 } 701 mThumbY = newThumbY; 702 // If the previous scrollTo is still pending 703 if (mScrollCompleted) { 704 scrollTo((float) mThumbY / (viewHeight - mThumbH)); 705 } 706 return true; 707 } 708 } 709 return false; 710 } 711 712 boolean isPointInside(float x, float y) { 713 boolean inTrack = false; 714 switch (mPosition) { 715 default: 716 case View.SCROLLBAR_POSITION_DEFAULT: 717 case View.SCROLLBAR_POSITION_RIGHT: 718 inTrack = x > mList.getWidth() - mThumbW; 719 break; 720 case View.SCROLLBAR_POSITION_LEFT: 721 inTrack = x < mThumbW; 722 break; 723 } 724 725 // Allow taps in the track to start moving. 726 return inTrack && (mTrackDrawable != null || y >= mThumbY && y <= mThumbY + mThumbH); 727 } 728 729 public class ScrollFade implements Runnable { 730 731 long mStartTime; 732 long mFadeDuration; 733 static final int ALPHA_MAX = 208; 734 static final long FADE_DURATION = 200; 735 736 void startFade() { 737 mFadeDuration = FADE_DURATION; 738 mStartTime = SystemClock.uptimeMillis(); 739 setState(STATE_EXIT); 740 } 741 742 int getAlpha() { 743 if (getState() != STATE_EXIT) { 744 return ALPHA_MAX; 745 } 746 int alpha; 747 long now = SystemClock.uptimeMillis(); 748 if (now > mStartTime + mFadeDuration) { 749 alpha = 0; 750 } else { 751 alpha = (int) (ALPHA_MAX - ((now - mStartTime) * ALPHA_MAX) / mFadeDuration); 752 } 753 return alpha; 754 } 755 756 public void run() { 757 if (getState() != STATE_EXIT) { 758 startFade(); 759 return; 760 } 761 762 if (getAlpha() > 0) { 763 mList.invalidate(); 764 } else { 765 setState(STATE_NONE); 766 } 767 } 768 } 769} 770