Home | History | Annotate | Download | only in launcher3
      1 /*
      2  * Copyright (C) 2015 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 package com.android.launcher3;
     17 
     18 import android.animation.AnimatorSet;
     19 import android.animation.ArgbEvaluator;
     20 import android.animation.ObjectAnimator;
     21 import android.animation.ValueAnimator;
     22 import android.content.res.Resources;
     23 import android.graphics.Canvas;
     24 import android.graphics.Color;
     25 import android.graphics.Paint;
     26 import android.graphics.Point;
     27 import android.graphics.Rect;
     28 import android.view.MotionEvent;
     29 import android.view.ViewConfiguration;
     30 
     31 import com.android.launcher3.util.Thunk;
     32 
     33 /**
     34  * The track and scrollbar that shows when you scroll the list.
     35  */
     36 public class BaseRecyclerViewFastScrollBar {
     37 
     38     public interface FastScrollFocusableView {
     39         void setFastScrollFocused(boolean focused, boolean animated);
     40     }
     41 
     42     private final static int MAX_TRACK_ALPHA = 30;
     43     private final static int SCROLL_BAR_VIS_DURATION = 150;
     44 
     45     @Thunk BaseRecyclerView mRv;
     46     private BaseRecyclerViewFastScrollPopup mPopup;
     47 
     48     private AnimatorSet mScrollbarAnimator;
     49 
     50     private int mThumbInactiveColor;
     51     private int mThumbActiveColor;
     52     @Thunk Point mThumbOffset = new Point(-1, -1);
     53     @Thunk Paint mThumbPaint;
     54     private Paint mTrackPaint;
     55     private int mThumbMinWidth;
     56     private int mThumbMaxWidth;
     57     @Thunk int mThumbWidth;
     58     @Thunk int mThumbHeight;
     59     // The inset is the buffer around which a point will still register as a click on the scrollbar
     60     private int mTouchInset;
     61     private boolean mIsDragging;
     62 
     63     // This is the offset from the top of the scrollbar when the user first starts touching.  To
     64     // prevent jumping, this offset is applied as the user scrolls.
     65     private int mTouchOffset;
     66 
     67     private Rect mInvalidateRect = new Rect();
     68     private Rect mTmpRect = new Rect();
     69 
     70     public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
     71         mRv = rv;
     72         mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
     73         mTrackPaint = new Paint();
     74         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
     75         mTrackPaint.setAlpha(0);
     76         mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
     77                 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
     78         mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
     79         mThumbPaint = new Paint();
     80         mThumbPaint.setColor(mThumbInactiveColor);
     81         mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
     82         mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
     83         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
     84         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
     85     }
     86 
     87     public void setScrollbarThumbOffset(int x, int y) {
     88         if (mThumbOffset.x == x && mThumbOffset.y == y) {
     89             return;
     90         }
     91         mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
     92         mThumbOffset.set(x, y);
     93         mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
     94                 mRv.getHeight()));
     95         mRv.invalidate(mInvalidateRect);
     96     }
     97 
     98     // Setter/getter for the search bar width for animations
     99     public void setWidth(int width) {
    100         mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
    101         mThumbWidth = width;
    102         mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
    103                 mRv.getHeight()));
    104         mRv.invalidate(mInvalidateRect);
    105     }
    106 
    107     public int getWidth() {
    108         return mThumbWidth;
    109     }
    110 
    111     // Setter/getter for the track background alpha for animations
    112     public void setTrackAlpha(int alpha) {
    113         mTrackPaint.setAlpha(alpha);
    114         mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
    115         mRv.invalidate(mInvalidateRect);
    116     }
    117 
    118     public int getTrackAlpha() {
    119         return mTrackPaint.getAlpha();
    120     }
    121 
    122     public int getThumbHeight() {
    123         return mThumbHeight;
    124     }
    125 
    126     public int getThumbMaxWidth() {
    127         return mThumbMaxWidth;
    128     }
    129 
    130     public boolean isDragging() {
    131         return mIsDragging;
    132     }
    133 
    134     /**
    135      * Handles the touch event and determines whether to show the fast scroller (or updates it if
    136      * it is already showing).
    137      */
    138     public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
    139         ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
    140 
    141         int action = ev.getAction();
    142         int y = (int) ev.getY();
    143         switch (action) {
    144             case MotionEvent.ACTION_DOWN:
    145                 if (isNearPoint(downX, downY)) {
    146                     mTouchOffset = downY - mThumbOffset.y;
    147                 }
    148                 break;
    149             case MotionEvent.ACTION_MOVE:
    150                 // Check if we should start scrolling
    151                 if (!mIsDragging && isNearPoint(downX, downY) &&
    152                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
    153                     mRv.getParent().requestDisallowInterceptTouchEvent(true);
    154                     mIsDragging = true;
    155                     mTouchOffset += (lastY - downY);
    156                     mPopup.animateVisibility(true);
    157                     animateScrollbar(true);
    158                 }
    159                 if (mIsDragging) {
    160                     // Update the fastscroller section name at this touch position
    161                     int top = mRv.getBackgroundPadding().top;
    162                     int bottom = mRv.getHeight() - mRv.getBackgroundPadding().bottom - mThumbHeight;
    163                     float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
    164                     String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
    165                             (bottom - top));
    166                     mPopup.setSectionName(sectionName);
    167                     mPopup.animateVisibility(!sectionName.isEmpty());
    168                     mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
    169                 }
    170                 break;
    171             case MotionEvent.ACTION_UP:
    172             case MotionEvent.ACTION_CANCEL:
    173                 mTouchOffset = 0;
    174                 if (mIsDragging) {
    175                     mIsDragging = false;
    176                     mPopup.animateVisibility(false);
    177                     animateScrollbar(false);
    178                 }
    179                 break;
    180         }
    181     }
    182 
    183     public void draw(Canvas canvas) {
    184         if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
    185             return;
    186         }
    187 
    188         // Draw the scroll bar track and thumb
    189         if (mTrackPaint.getAlpha() > 0) {
    190             canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
    191         }
    192         canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
    193                 mThumbOffset.y + mThumbHeight, mThumbPaint);
    194 
    195         // Draw the popup
    196         mPopup.draw(canvas);
    197     }
    198 
    199     /**
    200      * Animates the width and color of the scrollbar.
    201      */
    202     private void animateScrollbar(boolean isScrolling) {
    203         if (mScrollbarAnimator != null) {
    204             mScrollbarAnimator.cancel();
    205         }
    206         ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
    207                 isScrolling ? MAX_TRACK_ALPHA : 0);
    208         ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
    209                 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
    210         ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
    211                 mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
    212         colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    213             @Override
    214             public void onAnimationUpdate(ValueAnimator animator) {
    215                 mThumbPaint.setColor((Integer) animator.getAnimatedValue());
    216                 mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
    217                         mThumbOffset.y + mThumbHeight);
    218             }
    219         });
    220         mScrollbarAnimator = new AnimatorSet();
    221         mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
    222         mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
    223         mScrollbarAnimator.start();
    224     }
    225 
    226     /**
    227      * Returns whether the specified points are near the scroll bar bounds.
    228      */
    229     private boolean isNearPoint(int x, int y) {
    230         mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
    231                 mThumbOffset.y + mThumbHeight);
    232         mTmpRect.inset(mTouchInset, mTouchInset);
    233         return mTmpRect.contains(x, y);
    234     }
    235 }
    236