1 /* 2 * Copyright (C) 2017 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 17 package com.android.documentsui.dirlist; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ValueAnimator; 22 import android.animation.ValueAnimator.AnimatorUpdateListener; 23 import android.graphics.Canvas; 24 import android.graphics.drawable.Drawable; 25 import android.graphics.drawable.StateListDrawable; 26 import android.support.annotation.IntDef; 27 import android.support.annotation.Nullable; 28 import android.support.annotation.VisibleForTesting; 29 import android.support.v4.view.ViewCompat; 30 import android.support.v7.widget.RecyclerView; 31 import android.support.v7.widget.RecyclerView.ItemDecoration; 32 import android.support.v7.widget.RecyclerView.OnItemTouchListener; 33 import android.support.v7.widget.RecyclerView.OnScrollListener; 34 import android.view.MotionEvent; 35 36 /** 37 * Class responsible to animate and provide a fast scroller. 38 * 39 * Replace with supportlib version once released. See b/30713593. 40 */ 41 @VisibleForTesting 42 class FastScroller extends ItemDecoration implements OnItemTouchListener { 43 @IntDef({STATE_HIDDEN, STATE_VISIBLE, STATE_DRAGGING}) 44 private @interface State { } 45 // Scroll thumb not showing 46 private static final int STATE_HIDDEN = 0; 47 // Scroll thumb visible and moving along with the scrollbar 48 private static final int STATE_VISIBLE = 1; 49 // Scroll thumb being dragged by user 50 private static final int STATE_DRAGGING = 2; 51 52 @IntDef({DRAG_X, DRAG_Y, DRAG_NONE}) 53 private @interface DragState{ } 54 private static final int DRAG_NONE = 0; 55 private static final int DRAG_X = 1; 56 private static final int DRAG_Y = 2; 57 58 @IntDef({ANIMATION_STATE_OUT, ANIMATION_STATE_FADING_IN, ANIMATION_STATE_IN, 59 ANIMATION_STATE_FADING_OUT}) 60 private @interface AnimationState { } 61 private static final int ANIMATION_STATE_OUT = 0; 62 private static final int ANIMATION_STATE_FADING_IN = 1; 63 private static final int ANIMATION_STATE_IN = 2; 64 private static final int ANIMATION_STATE_FADING_OUT = 3; 65 66 private static final int SHOW_DURATION_MS = 500; 67 private static final int HIDE_DELAY_AFTER_VISIBLE_MS = 1500; 68 private static final int HIDE_DELAY_AFTER_DRAGGING_MS = 1200; 69 private static final int HIDE_DURATION_MS = 500; 70 private static final int SCROLLBAR_FULL_OPAQUE = 255; 71 72 private static final int[] PRESSED_STATE_SET = new int[]{android.R.attr.state_pressed}; 73 private static final int[] EMPTY_STATE_SET = new int[]{}; 74 75 private final int mScrollbarMinimumRange; 76 private final int mMargin; 77 78 // Final values for the vertical scroll bar 79 private final StateListDrawable mVerticalThumbDrawable; 80 private final Drawable mVerticalTrackDrawable; 81 private final int mVerticalThumbWidth; 82 private final int mVerticalTrackWidth; 83 84 // Final values for the horizontal scroll bar 85 private final StateListDrawable mHorizontalThumbDrawable; 86 private final Drawable mHorizontalTrackDrawable; 87 private final int mHorizontalThumbHeight; 88 private final int mHorizontalTrackHeight; 89 90 // Dynamic values for the vertical scroll bar 91 @VisibleForTesting int mVerticalThumbHeight; 92 @VisibleForTesting int mVerticalThumbCenterY; 93 @VisibleForTesting float mVerticalDragY; 94 95 // Dynamic values for the horizontal scroll bar 96 @VisibleForTesting int mHorizontalThumbWidth; 97 @VisibleForTesting int mHorizontalThumbCenterX; 98 @VisibleForTesting float mHorizontalDragX; 99 100 private int mRecyclerViewWidth = 0; 101 private int mRecyclerViewHeight = 0; 102 103 private RecyclerView mRecyclerView; 104 /** 105 * Whether the document is long/wide enough to require scrolling. If not, we don't show the 106 * relevant scroller. 107 */ 108 private boolean mNeedVerticalScrollbar = false; 109 private boolean mNeedHorizontalScrollbar = false; 110 @State private int mState = STATE_HIDDEN; 111 @DragState private int mDragState = DRAG_NONE; 112 113 private final int[] mVerticalRange = new int[2]; 114 private final int[] mHorizontalRange = new int[2]; 115 private final ValueAnimator mShowHideAnimator = ValueAnimator.ofFloat(0, 1); 116 @AnimationState private int mAnimationState = ANIMATION_STATE_OUT; 117 private final Runnable mHideRunnable = new Runnable() { 118 @Override 119 public void run() { 120 hide(HIDE_DURATION_MS); 121 } 122 }; 123 private final OnScrollListener mOnScrollListener = new OnScrollListener() { 124 @Override 125 public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 126 updateScrollPosition(recyclerView.computeHorizontalScrollOffset(), 127 recyclerView.computeVerticalScrollOffset()); 128 } 129 }; 130 FastScroller(RecyclerView recyclerView, StateListDrawable verticalThumbDrawable, Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, Drawable horizontalTrackDrawable, int defaultWidth, int scrollbarMinimumRange, int margin)131 FastScroller(RecyclerView recyclerView, StateListDrawable verticalThumbDrawable, 132 Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, 133 Drawable horizontalTrackDrawable, int defaultWidth, int scrollbarMinimumRange, 134 int margin) { 135 mVerticalThumbDrawable = verticalThumbDrawable; 136 mVerticalTrackDrawable = verticalTrackDrawable; 137 mHorizontalThumbDrawable = horizontalThumbDrawable; 138 mHorizontalTrackDrawable = horizontalTrackDrawable; 139 mVerticalThumbWidth = Math.max(defaultWidth, verticalThumbDrawable.getIntrinsicWidth()); 140 mVerticalTrackWidth = Math.max(defaultWidth, verticalTrackDrawable.getIntrinsicWidth()); 141 mHorizontalThumbHeight = Math 142 .max(defaultWidth, horizontalThumbDrawable.getIntrinsicWidth()); 143 mHorizontalTrackHeight = Math 144 .max(defaultWidth, horizontalTrackDrawable.getIntrinsicWidth()); 145 mScrollbarMinimumRange = scrollbarMinimumRange; 146 mMargin = margin; 147 mVerticalThumbDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE); 148 mVerticalTrackDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE); 149 150 mShowHideAnimator.addListener(new AnimatorListener()); 151 mShowHideAnimator.addUpdateListener(new AnimatorUpdater()); 152 153 attachToRecyclerView(recyclerView); 154 } 155 attachToRecyclerView(@ullable RecyclerView recyclerView)156 public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { 157 if (mRecyclerView == recyclerView) { 158 return; // nothing to do 159 } 160 if (mRecyclerView != null) { 161 destroyCallbacks(); 162 } 163 mRecyclerView = recyclerView; 164 if (mRecyclerView != null) { 165 setupCallbacks(); 166 } 167 } 168 setupCallbacks()169 private void setupCallbacks() { 170 mRecyclerView.addItemDecoration(this); 171 mRecyclerView.addOnItemTouchListener(this); 172 mRecyclerView.addOnScrollListener(mOnScrollListener); 173 } 174 destroyCallbacks()175 private void destroyCallbacks() { 176 mRecyclerView.removeItemDecoration(this); 177 mRecyclerView.removeOnItemTouchListener(this); 178 mRecyclerView.removeOnScrollListener(mOnScrollListener); 179 cancelHide(); 180 } 181 requestRedraw()182 private void requestRedraw() { 183 mRecyclerView.invalidate(); 184 } 185 setState(@tate int state)186 private void setState(@State int state) { 187 if (state == STATE_DRAGGING && mState != STATE_DRAGGING) { 188 mVerticalThumbDrawable.setState(PRESSED_STATE_SET); 189 cancelHide(); 190 } 191 192 if (state == STATE_HIDDEN) { 193 requestRedraw(); 194 } else { 195 show(); 196 } 197 198 if (mState == STATE_DRAGGING && state != STATE_DRAGGING) { 199 mVerticalThumbDrawable.setState(EMPTY_STATE_SET); 200 resetHideDelay(HIDE_DELAY_AFTER_DRAGGING_MS); 201 } else if (state == STATE_VISIBLE) { 202 resetHideDelay(HIDE_DELAY_AFTER_VISIBLE_MS); 203 } 204 mState = state; 205 } 206 isLayoutRTL()207 private boolean isLayoutRTL() { 208 return ViewCompat.getLayoutDirection(mRecyclerView) == ViewCompat.LAYOUT_DIRECTION_RTL; 209 } 210 isDragging()211 public boolean isDragging() { 212 return mState == STATE_DRAGGING; 213 } 214 isVisible()215 @VisibleForTesting boolean isVisible() { 216 return mState == STATE_VISIBLE; 217 } 218 isHidden()219 @VisibleForTesting boolean isHidden() { 220 return mState == STATE_HIDDEN; 221 } 222 223 show()224 public void show() { 225 switch (mAnimationState) { 226 case ANIMATION_STATE_FADING_OUT: 227 mShowHideAnimator.cancel(); 228 // no break 229 case ANIMATION_STATE_OUT: 230 mAnimationState = ANIMATION_STATE_FADING_IN; 231 mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 1); 232 mShowHideAnimator.setDuration(SHOW_DURATION_MS); 233 mShowHideAnimator.setStartDelay(0); 234 mShowHideAnimator.start(); 235 break; 236 } 237 } 238 hide()239 public void hide() { 240 hide(0); 241 } 242 243 @VisibleForTesting hide(int duration)244 void hide(int duration) { 245 switch (mAnimationState) { 246 case ANIMATION_STATE_FADING_IN: 247 mShowHideAnimator.cancel(); 248 // no break 249 case ANIMATION_STATE_IN: 250 mAnimationState = ANIMATION_STATE_FADING_OUT; 251 mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 0); 252 mShowHideAnimator.setDuration(duration); 253 mShowHideAnimator.start(); 254 break; 255 } 256 } 257 cancelHide()258 private void cancelHide() { 259 mRecyclerView.removeCallbacks(mHideRunnable); 260 } 261 resetHideDelay(int delay)262 private void resetHideDelay(int delay) { 263 cancelHide(); 264 mRecyclerView.postDelayed(mHideRunnable, delay); 265 } 266 267 @Override onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state)268 public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) { 269 if (mRecyclerViewWidth != mRecyclerView.getWidth() 270 || mRecyclerViewHeight != mRecyclerView.getHeight()) { 271 mRecyclerViewWidth = mRecyclerView.getWidth(); 272 mRecyclerViewHeight = mRecyclerView.getHeight(); 273 // This is due to the different events ordering when keyboard is opened or 274 // retracted vs rotate. Hence to avoid corner cases we just disable the 275 // scroller when size changed, and wait until the scroll position is recomputed 276 // before showing it back. 277 setState(STATE_HIDDEN); 278 return; 279 } 280 281 if (mAnimationState != ANIMATION_STATE_OUT) { 282 if (mNeedVerticalScrollbar) { 283 drawVerticalScrollbar(canvas); 284 } 285 if (mNeedHorizontalScrollbar) { 286 drawHorizontalScrollbar(canvas); 287 } 288 } 289 } 290 drawVerticalScrollbar(Canvas canvas)291 private void drawVerticalScrollbar(Canvas canvas) { 292 int viewWidth = mRecyclerViewWidth; 293 294 int left = viewWidth - mVerticalThumbWidth; 295 int top = mVerticalThumbCenterY - mVerticalThumbHeight / 2; 296 mVerticalThumbDrawable.setBounds(0, 0, mVerticalThumbWidth, mVerticalThumbHeight); 297 mVerticalTrackDrawable 298 .setBounds(0, 0, mVerticalTrackWidth, mRecyclerViewHeight); 299 300 if (isLayoutRTL()) { 301 mVerticalTrackDrawable.draw(canvas); 302 canvas.translate(mVerticalThumbWidth, top); 303 canvas.scale(-1, 1); 304 mVerticalThumbDrawable.draw(canvas); 305 canvas.scale(1, 1); 306 canvas.translate(-mVerticalThumbWidth, -top); 307 } else { 308 canvas.translate(left, 0); 309 mVerticalTrackDrawable.draw(canvas); 310 canvas.translate(0, top); 311 mVerticalThumbDrawable.draw(canvas); 312 canvas.translate(-left, -top); 313 } 314 } 315 drawHorizontalScrollbar(Canvas canvas)316 private void drawHorizontalScrollbar(Canvas canvas) { 317 int viewHeight = mRecyclerViewHeight; 318 319 int top = viewHeight - mHorizontalThumbHeight; 320 int left = mHorizontalThumbCenterX - mHorizontalThumbWidth / 2; 321 mHorizontalThumbDrawable.setBounds(0, 0, mHorizontalThumbWidth, mHorizontalThumbHeight); 322 mHorizontalTrackDrawable 323 .setBounds(0, 0, mRecyclerViewWidth, mHorizontalTrackHeight); 324 325 canvas.translate(0, top); 326 mHorizontalTrackDrawable.draw(canvas); 327 canvas.translate(left, 0); 328 mHorizontalThumbDrawable.draw(canvas); 329 canvas.translate(-left, -top); 330 } 331 332 /** 333 * Notify the scroller of external change of the scroll, e.g. through dragging or flinging on 334 * the view itself. 335 * 336 * @param offsetX The new scroll X offset. 337 * @param offsetY The new scroll Y offset. 338 */ updateScrollPosition(int offsetX, int offsetY)339 void updateScrollPosition(int offsetX, int offsetY) { 340 int verticalContentLength = mRecyclerView.computeVerticalScrollRange(); 341 int verticalVisibleLength = mRecyclerViewHeight; 342 mNeedVerticalScrollbar = verticalContentLength - verticalVisibleLength > 0 343 && mRecyclerViewHeight >= mScrollbarMinimumRange; 344 345 int horizontalContentLength = mRecyclerView.computeHorizontalScrollRange(); 346 int horizontalVisibleLength = mRecyclerViewWidth; 347 mNeedHorizontalScrollbar = horizontalContentLength - horizontalVisibleLength > 0 348 && mRecyclerViewWidth >= mScrollbarMinimumRange; 349 350 if (!mNeedVerticalScrollbar && !mNeedHorizontalScrollbar) { 351 if (mState != STATE_HIDDEN) { 352 setState(STATE_HIDDEN); 353 } 354 return; 355 } 356 357 if (mNeedVerticalScrollbar) { 358 float middleScreenPos = offsetY + verticalVisibleLength / 2.0f; 359 mVerticalThumbCenterY = 360 (int) ((verticalVisibleLength * middleScreenPos) / verticalContentLength); 361 mVerticalThumbHeight = Math.min(verticalVisibleLength, 362 (verticalVisibleLength * verticalVisibleLength) / verticalContentLength); 363 } 364 365 if (mNeedHorizontalScrollbar) { 366 float middleScreenPos = offsetX + horizontalVisibleLength / 2.0f; 367 mHorizontalThumbCenterX = 368 (int) ((horizontalVisibleLength * middleScreenPos) / horizontalContentLength); 369 mHorizontalThumbWidth = Math.min(horizontalVisibleLength, 370 (horizontalVisibleLength * horizontalVisibleLength) / horizontalContentLength); 371 } 372 373 if (mState == STATE_HIDDEN || mState == STATE_VISIBLE) { 374 setState(STATE_VISIBLE); 375 } 376 } 377 378 @Override onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent ev)379 public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent ev) { 380 final boolean handled; 381 if (mState == STATE_VISIBLE) { 382 boolean insideVerticalThumb = isPointInsideVerticalThumb(ev.getX(), ev.getY()); 383 boolean insideHorizontalThumb = isPointInsideHorizontalThumb(ev.getX(), ev.getY()); 384 if (ev.getAction() == MotionEvent.ACTION_DOWN 385 && (insideVerticalThumb || insideHorizontalThumb)) { 386 if (insideHorizontalThumb) { 387 mDragState = DRAG_X; 388 mHorizontalDragX = (int) ev.getX(); 389 } else if (insideVerticalThumb) { 390 mDragState = DRAG_Y; 391 mVerticalDragY = (int) ev.getY(); 392 } 393 394 setState(STATE_DRAGGING); 395 handled = true; 396 } else { 397 handled = false; 398 } 399 } else if (mState == STATE_DRAGGING) { 400 handled = true; 401 } else { 402 handled = false; 403 } 404 return handled; 405 } 406 407 @Override onTouchEvent(RecyclerView recyclerView, MotionEvent me)408 public void onTouchEvent(RecyclerView recyclerView, MotionEvent me) { 409 if (mState == STATE_HIDDEN) { 410 return; 411 } 412 413 if (me.getAction() == MotionEvent.ACTION_DOWN) { 414 boolean insideVerticalThumb = isPointInsideVerticalThumb(me.getX(), me.getY()); 415 boolean insideHorizontalThumb = isPointInsideHorizontalThumb(me.getX(), me.getY()); 416 if (insideVerticalThumb || insideHorizontalThumb) { 417 if (insideHorizontalThumb) { 418 mDragState = DRAG_X; 419 mHorizontalDragX = (int) me.getX(); 420 } else if (insideVerticalThumb) { 421 mDragState = DRAG_Y; 422 mVerticalDragY = (int) me.getY(); 423 } 424 setState(STATE_DRAGGING); 425 } 426 } else if (me.getAction() == MotionEvent.ACTION_UP && mState == STATE_DRAGGING) { 427 mVerticalDragY = 0; 428 mHorizontalDragX = 0; 429 setState(STATE_VISIBLE); 430 mDragState = DRAG_NONE; 431 } else if (me.getAction() == MotionEvent.ACTION_MOVE && mState == STATE_DRAGGING) { 432 show(); 433 if (mDragState == DRAG_X) { 434 horizontalScrollTo(me.getX()); 435 } 436 if (mDragState == DRAG_Y) { 437 verticalScrollTo(me.getY()); 438 } 439 } 440 } 441 442 @Override onRequestDisallowInterceptTouchEvent(boolean disallowIntercept)443 public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { } 444 verticalScrollTo(float y)445 private void verticalScrollTo(float y) { 446 final int[] scrollbarRange = getVerticalRange(); 447 y = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], y)); 448 if (Math.abs(mVerticalThumbCenterY - y) < 2) { 449 return; 450 } 451 int scrollingBy = scrollTo(mVerticalDragY, y, scrollbarRange, 452 mRecyclerView.computeVerticalScrollRange(), 453 mRecyclerView.computeVerticalScrollOffset(), mRecyclerViewHeight); 454 if (scrollingBy != 0) { 455 mRecyclerView.scrollBy(0, scrollingBy); 456 } 457 mVerticalDragY = y; 458 } 459 horizontalScrollTo(float x)460 private void horizontalScrollTo(float x) { 461 final int[] scrollbarRange = getHorizontalRange(); 462 x = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], x)); 463 if (Math.abs(mHorizontalThumbCenterX - x) < 2) { 464 return; 465 } 466 467 int scrollingBy = scrollTo(mHorizontalDragX, x, scrollbarRange, 468 mRecyclerView.computeHorizontalScrollRange(), 469 mRecyclerView.computeHorizontalScrollOffset(), mRecyclerViewWidth); 470 if (scrollingBy != 0) { 471 mRecyclerView.scrollBy(scrollingBy, 0); 472 } 473 474 mHorizontalDragX = x; 475 } 476 scrollTo(float oldDragPos, float newDragPos, int[] scrollbarRange, int scrollRange, int scrollOffset, int viewLength)477 private int scrollTo(float oldDragPos, float newDragPos, int[] scrollbarRange, int scrollRange, 478 int scrollOffset, int viewLength) { 479 int scrollbarLength = scrollbarRange[1] - scrollbarRange[0]; 480 if (scrollbarLength == 0) { 481 return 0; 482 } 483 float percentage = ((newDragPos - oldDragPos) / (float) scrollbarLength); 484 int totalPossibleOffset = scrollRange - viewLength; 485 int scrollingBy = (int) (percentage * totalPossibleOffset); 486 int absoluteOffset = scrollOffset + scrollingBy; 487 if (absoluteOffset < totalPossibleOffset && absoluteOffset >= 0) { 488 return scrollingBy; 489 } else { 490 return 0; 491 } 492 } 493 494 @VisibleForTesting isPointInsideVerticalThumb(float x, float y)495 boolean isPointInsideVerticalThumb(float x, float y) { 496 return (isLayoutRTL() ? x <= mVerticalThumbWidth / 2 497 : x >= mRecyclerViewWidth - mVerticalThumbWidth) 498 && y >= mVerticalThumbCenterY - mVerticalThumbHeight / 2 499 && y <= mVerticalThumbCenterY + mVerticalThumbHeight / 2; 500 } 501 502 @VisibleForTesting isPointInsideHorizontalThumb(float x, float y)503 boolean isPointInsideHorizontalThumb(float x, float y) { 504 return (y >= mRecyclerViewHeight - mHorizontalThumbHeight) 505 && x >= mHorizontalThumbCenterX - mHorizontalThumbWidth / 2 506 && x <= mHorizontalThumbCenterX + mHorizontalThumbWidth / 2; 507 } 508 509 @VisibleForTesting getHorizontalTrackDrawable()510 Drawable getHorizontalTrackDrawable() { 511 return mHorizontalTrackDrawable; 512 } 513 514 @VisibleForTesting getHorizontalThumbDrawable()515 Drawable getHorizontalThumbDrawable() { 516 return mHorizontalThumbDrawable; 517 } 518 519 @VisibleForTesting getVerticalTrackDrawable()520 Drawable getVerticalTrackDrawable() { 521 return mVerticalTrackDrawable; 522 } 523 524 @VisibleForTesting getVerticalThumbDrawable()525 Drawable getVerticalThumbDrawable() { 526 return mVerticalThumbDrawable; 527 } 528 529 /** 530 * Gets the (min, max) vertical positions of the vertical scroll bar. 531 */ getVerticalRange()532 private int[] getVerticalRange() { 533 mVerticalRange[0] = mMargin; 534 mVerticalRange[1] = mRecyclerViewHeight - mMargin; 535 return mVerticalRange; 536 } 537 538 /** 539 * Gets the (min, max) horizontal positions of the horizontal scroll bar. 540 */ getHorizontalRange()541 private int[] getHorizontalRange() { 542 mHorizontalRange[0] = mMargin; 543 mHorizontalRange[1] = mRecyclerViewWidth - mMargin; 544 return mHorizontalRange; 545 } 546 547 private class AnimatorListener extends AnimatorListenerAdapter { 548 549 private boolean mCanceled = false; 550 551 @Override onAnimationEnd(Animator animation)552 public void onAnimationEnd(Animator animation) { 553 // Cancel is always followed by a new directive, so don't update state. 554 if (mCanceled) { 555 mCanceled = false; 556 return; 557 } 558 if ((float) mShowHideAnimator.getAnimatedValue() == 0) { 559 mAnimationState = ANIMATION_STATE_OUT; 560 setState(STATE_HIDDEN); 561 } else { 562 mAnimationState = ANIMATION_STATE_IN; 563 requestRedraw(); 564 } 565 } 566 567 @Override onAnimationCancel(Animator animation)568 public void onAnimationCancel(Animator animation) { 569 mCanceled = true; 570 } 571 } 572 573 private class AnimatorUpdater implements AnimatorUpdateListener { 574 575 @Override onAnimationUpdate(ValueAnimator valueAnimator)576 public void onAnimationUpdate(ValueAnimator valueAnimator) { 577 int alpha = (int) (SCROLLBAR_FULL_OPAQUE * ((float) valueAnimator.getAnimatedValue())); 578 mVerticalThumbDrawable.setAlpha(alpha); 579 mVerticalTrackDrawable.setAlpha(alpha); 580 requestRedraw(); 581 } 582 } 583 }