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 }