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