2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.android.launcher3;
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.Path;
27 import android.graphics.Point;
28 import android.graphics.Rect;
29 import android.view.MotionEvent;
30 import android.view.ViewConfiguration;
32 import com.android.launcher3.util.Thunk;
35 * The track and scrollbar that shows when you scroll the list.
37 public class BaseRecyclerViewFastScrollBar {
39 public interface FastScrollFocusableView {
40 void setFastScrollFocused(boolean focused, boolean animated);
43 private final static int MAX_TRACK_ALPHA = 30;
44 private final static int SCROLL_BAR_VIS_DURATION = 150;
46 @Thunk BaseRecyclerView mRv;
47 private BaseRecyclerViewFastScrollPopup mPopup;
49 private AnimatorSet mScrollbarAnimator;
51 private int mThumbInactiveColor;
52 private int mThumbActiveColor;
53 @Thunk Point mThumbOffset = new Point(-1, -1);
54 @Thunk Paint mThumbPaint;
55 private int mThumbMinWidth;
56 private int mThumbMaxWidth;
57 @Thunk int mThumbWidth;
58 @Thunk int mThumbHeight;
59 private int mThumbCurvature;
60 private Path mThumbPath = new Path();
61 private Paint mTrackPaint;
62 private int mTrackWidth;
63 private float mLastTouchY;
64 // The inset is the buffer around which a point will still register as a click on the scrollbar
65 private int mTouchInset;
66 private boolean mIsDragging;
67 private boolean mIsThumbDetached;
68 private boolean mCanThumbDetach;
70 // This is the offset from the top of the scrollbar when the user first starts touching. To
71 // prevent jumping, this offset is applied as the user scrolls.
72 private int mTouchOffset;
74 private Rect mInvalidateRect = new Rect();
75 private Rect mTmpRect = new Rect();
77 public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
79 mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
80 mTrackPaint = new Paint();
81 mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
82 mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
83 mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
84 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
85 mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
86 mThumbPaint = new Paint();
87 mThumbPaint.setAntiAlias(true);
88 mThumbPaint.setColor(mThumbInactiveColor);
89 mThumbPaint.setStyle(Paint.Style.FILL);
90 mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
91 mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
92 mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
93 mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
94 mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
97 public void setDetachThumbOnFastScroll() {
98 mCanThumbDetach = true;
101 public void reattachThumbToScroll() {
102 mIsThumbDetached = false;
105 public void setThumbOffset(int x, int y) {
106 if (mThumbOffset.x == x && mThumbOffset.y == y) {
109 mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
110 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
111 mThumbOffset.set(x, y);
113 mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
114 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
115 mRv.invalidate(mInvalidateRect);
118 public Point getThumbOffset() {
122 // Setter/getter for the thumb bar width for animations
123 public void setThumbWidth(int width) {
124 mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
125 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
128 mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
129 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
130 mRv.invalidate(mInvalidateRect);
133 public int getThumbWidth() {
137 // Setter/getter for the track bar width for animations
138 public void setTrackWidth(int width) {
139 mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
143 mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
145 mRv.invalidate(mInvalidateRect);
148 public int getTrackWidth() {
152 public int getThumbHeight() {
156 public int getThumbMaxWidth() {
157 return mThumbMaxWidth;
160 public float getLastTouchY() {
164 public boolean isDraggingThumb() {
168 public boolean isThumbDetached() {
169 return mIsThumbDetached;
173 * Handles the touch event and determines whether to show the fast scroller (or updates it if
174 * it is already showing).
176 public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
177 ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
179 int action = ev.getAction();
180 int y = (int) ev.getY();
182 case MotionEvent.ACTION_DOWN:
183 if (isNearPoint(downX, downY)) {
184 mTouchOffset = downY - mThumbOffset.y;
187 case MotionEvent.ACTION_MOVE:
188 // Check if we should start scrolling
189 if (!mIsDragging && isNearPoint(downX, downY) &&
190 Math.abs(y - downY) > config.getScaledTouchSlop()) {
191 mRv.getParent().requestDisallowInterceptTouchEvent(true);
193 if (mCanThumbDetach) {
194 mIsThumbDetached = true;
196 mTouchOffset += (lastY - downY);
197 mPopup.animateVisibility(true);
198 animateScrollbar(true);
201 // Update the fastscroller section name at this touch position
202 int top = mRv.getBackgroundPadding().top;
203 int bottom = mRv.getHeight() - mRv.getBackgroundPadding().bottom - mThumbHeight;
204 float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
205 String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
207 mPopup.setSectionName(sectionName);
208 mPopup.animateVisibility(!sectionName.isEmpty());
209 mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
210 mLastTouchY = boundedY;
213 case MotionEvent.ACTION_UP:
214 case MotionEvent.ACTION_CANCEL:
219 mPopup.animateVisibility(false);
220 animateScrollbar(false);
226 public void draw(Canvas canvas) {
227 if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
231 // Draw the scroll bar track and thumb
232 if (mTrackPaint.getAlpha() > 0) {
233 canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
235 canvas.drawPath(mThumbPath, mThumbPaint);
242 * Animates the width and color of the scrollbar.
244 private void animateScrollbar(boolean isScrolling) {
245 if (mScrollbarAnimator != null) {
246 mScrollbarAnimator.cancel();
249 mScrollbarAnimator = new AnimatorSet();
250 ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
251 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
252 ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
253 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
254 mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
255 if (mThumbActiveColor != mThumbInactiveColor) {
256 ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
257 mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
258 colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
260 public void onAnimationUpdate(ValueAnimator animator) {
261 mThumbPaint.setColor((Integer) animator.getAnimatedValue());
262 mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
263 mThumbOffset.y + mThumbHeight);
266 mScrollbarAnimator.play(colorAnimation);
268 mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
269 mScrollbarAnimator.start();
273 * Updates the path for the thumb drawable.
275 private void updateThumbPath() {
276 mThumbCurvature = mThumbMaxWidth - mThumbWidth;
278 mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y); // tr
279 mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight); // br
280 mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight); // bl
281 mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
282 mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
283 mThumbOffset.x, mThumbOffset.y); // bl2tl
288 * Returns whether the specified points are near the scroll bar bounds.
290 private boolean isNearPoint(int x, int y) {
291 mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
292 mThumbOffset.y + mThumbHeight);
293 mTmpRect.inset(mTouchInset, mTouchInset);
294 return mTmpRect.contains(x, y);