blob: f76aed7ad4fb68d7b29b238f2c6029e19bea53f9 [file] [log] [blame]
Winson Chungb1777442015-06-16 13:35:04 -07001/*
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 */
16package com.android.launcher3;
17
Winson Chungb1777442015-06-16 13:35:04 -070018import android.animation.AnimatorSet;
19import android.animation.ArgbEvaluator;
20import android.animation.ObjectAnimator;
21import android.animation.ValueAnimator;
22import android.content.res.Resources;
23import android.graphics.Canvas;
24import android.graphics.Color;
25import android.graphics.Paint;
Winson67795952015-08-20 12:23:52 -070026import android.graphics.Path;
Winson Chungb1777442015-06-16 13:35:04 -070027import android.graphics.Point;
28import android.graphics.Rect;
29import android.view.MotionEvent;
30import android.view.ViewConfiguration;
31
Sunny Goyalb713ad42015-06-24 11:45:32 -070032import com.android.launcher3.util.Thunk;
33
Winson Chungb1777442015-06-16 13:35:04 -070034/**
35 * The track and scrollbar that shows when you scroll the list.
36 */
37public class BaseRecyclerViewFastScrollBar {
38
39 public interface FastScrollFocusableView {
40 void setFastScrollFocused(boolean focused, boolean animated);
41 }
42
43 private final static int MAX_TRACK_ALPHA = 30;
44 private final static int SCROLL_BAR_VIS_DURATION = 150;
45
Sunny Goyalb713ad42015-06-24 11:45:32 -070046 @Thunk BaseRecyclerView mRv;
Winson Chungb1777442015-06-16 13:35:04 -070047 private BaseRecyclerViewFastScrollPopup mPopup;
48
49 private AnimatorSet mScrollbarAnimator;
50
51 private int mThumbInactiveColor;
52 private int mThumbActiveColor;
Sunny Goyalb713ad42015-06-24 11:45:32 -070053 @Thunk Point mThumbOffset = new Point(-1, -1);
54 @Thunk Paint mThumbPaint;
Winson Chungb1777442015-06-16 13:35:04 -070055 private int mThumbMinWidth;
56 private int mThumbMaxWidth;
Sunny Goyalb713ad42015-06-24 11:45:32 -070057 @Thunk int mThumbWidth;
58 @Thunk int mThumbHeight;
Winson67795952015-08-20 12:23:52 -070059 private int mThumbCurvature;
60 private Path mThumbPath = new Path();
61 private Paint mTrackPaint;
62 private int mTrackWidth;
Winsond2eb49e2015-08-18 17:43:02 -070063 private float mLastTouchY;
Winson Chungb1777442015-06-16 13:35:04 -070064 // 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;
Winsond2eb49e2015-08-18 17:43:02 -070067 private boolean mIsThumbDetached;
68 private boolean mCanThumbDetach;
Winson Chungb1777442015-06-16 13:35:04 -070069
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;
73
74 private Rect mInvalidateRect = new Rect();
75 private Rect mTmpRect = new Rect();
76
77 public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
78 mRv = rv;
79 mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
80 mTrackPaint = new Paint();
81 mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
Winson67795952015-08-20 12:23:52 -070082 mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
Winson Chungb1777442015-06-16 13:35:04 -070083 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();
Winson67795952015-08-20 12:23:52 -070087 mThumbPaint.setAntiAlias(true);
Winson Chungb1777442015-06-16 13:35:04 -070088 mThumbPaint.setColor(mThumbInactiveColor);
Winson67795952015-08-20 12:23:52 -070089 mThumbPaint.setStyle(Paint.Style.FILL);
Winson Chungb1777442015-06-16 13:35:04 -070090 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);
Winson67795952015-08-20 12:23:52 -070093 mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
Winson Chungb1777442015-06-16 13:35:04 -070094 mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
95 }
96
Winsond2eb49e2015-08-18 17:43:02 -070097 public void setDetachThumbOnFastScroll() {
98 mCanThumbDetach = true;
99 }
100
101 public void reattachThumbToScroll() {
102 mIsThumbDetached = false;
103 }
104
105 public void setThumbOffset(int x, int y) {
Winson Chungb1777442015-06-16 13:35:04 -0700106 if (mThumbOffset.x == x && mThumbOffset.y == y) {
107 return;
108 }
Winson67795952015-08-20 12:23:52 -0700109 mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
110 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
Winson Chungb1777442015-06-16 13:35:04 -0700111 mThumbOffset.set(x, y);
Winson67795952015-08-20 12:23:52 -0700112 updateThumbPath();
113 mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
114 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
Winson Chungb1777442015-06-16 13:35:04 -0700115 mRv.invalidate(mInvalidateRect);
116 }
117
Winsond2eb49e2015-08-18 17:43:02 -0700118 public Point getThumbOffset() {
119 return mThumbOffset;
120 }
121
Winson67795952015-08-20 12:23:52 -0700122 // Setter/getter for the thumb bar width for animations
Winsond2eb49e2015-08-18 17:43:02 -0700123 public void setThumbWidth(int width) {
Winson67795952015-08-20 12:23:52 -0700124 mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
125 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
Winson Chungb1777442015-06-16 13:35:04 -0700126 mThumbWidth = width;
Winson67795952015-08-20 12:23:52 -0700127 updateThumbPath();
128 mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
129 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
Winson Chungb1777442015-06-16 13:35:04 -0700130 mRv.invalidate(mInvalidateRect);
131 }
132
Winsond2eb49e2015-08-18 17:43:02 -0700133 public int getThumbWidth() {
Winson Chungb1777442015-06-16 13:35:04 -0700134 return mThumbWidth;
135 }
136
Winson67795952015-08-20 12:23:52 -0700137 // 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,
140 mRv.getHeight());
141 mTrackWidth = width;
142 updateThumbPath();
143 mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
144 mRv.getHeight());
Winson Chungb1777442015-06-16 13:35:04 -0700145 mRv.invalidate(mInvalidateRect);
146 }
147
Winson67795952015-08-20 12:23:52 -0700148 public int getTrackWidth() {
149 return mTrackWidth;
Winson Chungb1777442015-06-16 13:35:04 -0700150 }
151
152 public int getThumbHeight() {
153 return mThumbHeight;
154 }
155
156 public int getThumbMaxWidth() {
157 return mThumbMaxWidth;
158 }
159
Winsond2eb49e2015-08-18 17:43:02 -0700160 public float getLastTouchY() {
161 return mLastTouchY;
162 }
163
164 public boolean isDraggingThumb() {
Winson Chungb1777442015-06-16 13:35:04 -0700165 return mIsDragging;
166 }
167
Winsond2eb49e2015-08-18 17:43:02 -0700168 public boolean isThumbDetached() {
169 return mIsThumbDetached;
170 }
171
Winson Chungb1777442015-06-16 13:35:04 -0700172 /**
173 * Handles the touch event and determines whether to show the fast scroller (or updates it if
174 * it is already showing).
175 */
176 public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
177 ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
178
179 int action = ev.getAction();
180 int y = (int) ev.getY();
181 switch (action) {
182 case MotionEvent.ACTION_DOWN:
183 if (isNearPoint(downX, downY)) {
184 mTouchOffset = downY - mThumbOffset.y;
185 }
186 break;
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);
192 mIsDragging = true;
Winsond2eb49e2015-08-18 17:43:02 -0700193 if (mCanThumbDetach) {
194 mIsThumbDetached = true;
195 }
Winson Chungb1777442015-06-16 13:35:04 -0700196 mTouchOffset += (lastY - downY);
197 mPopup.animateVisibility(true);
198 animateScrollbar(true);
199 }
200 if (mIsDragging) {
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) /
206 (bottom - top));
207 mPopup.setSectionName(sectionName);
208 mPopup.animateVisibility(!sectionName.isEmpty());
209 mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
Winsond2eb49e2015-08-18 17:43:02 -0700210 mLastTouchY = boundedY;
Winson Chungb1777442015-06-16 13:35:04 -0700211 }
212 break;
213 case MotionEvent.ACTION_UP:
214 case MotionEvent.ACTION_CANCEL:
Winson Chungb1777442015-06-16 13:35:04 -0700215 mTouchOffset = 0;
Winsond2eb49e2015-08-18 17:43:02 -0700216 mLastTouchY = 0;
Winson Chung4c7fc622015-06-24 16:59:31 -0700217 if (mIsDragging) {
218 mIsDragging = false;
219 mPopup.animateVisibility(false);
220 animateScrollbar(false);
221 }
Winson Chungb1777442015-06-16 13:35:04 -0700222 break;
223 }
224 }
225
226 public void draw(Canvas canvas) {
227 if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
228 return;
229 }
230
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);
234 }
Winson67795952015-08-20 12:23:52 -0700235 canvas.drawPath(mThumbPath, mThumbPaint);
Winson Chungb1777442015-06-16 13:35:04 -0700236
237 // Draw the popup
238 mPopup.draw(canvas);
239 }
240
241 /**
242 * Animates the width and color of the scrollbar.
243 */
244 private void animateScrollbar(boolean isScrolling) {
245 if (mScrollbarAnimator != null) {
246 mScrollbarAnimator.cancel();
247 }
Winson67795952015-08-20 12:23:52 -0700248
249 mScrollbarAnimator = new AnimatorSet();
250 ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
251 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
Winsond2eb49e2015-08-18 17:43:02 -0700252 ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
Winson Chungb1777442015-06-16 13:35:04 -0700253 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
Winson67795952015-08-20 12:23:52 -0700254 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() {
259 @Override
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);
264 }
265 });
266 mScrollbarAnimator.play(colorAnimation);
267 }
Winson Chungb1777442015-06-16 13:35:04 -0700268 mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
269 mScrollbarAnimator.start();
270 }
271
272 /**
Winson67795952015-08-20 12:23:52 -0700273 * Updates the path for the thumb drawable.
274 */
275 private void updateThumbPath() {
276 mThumbCurvature = mThumbMaxWidth - mThumbWidth;
277 mThumbPath.reset();
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
284 mThumbPath.close();
285 }
286
287 /**
Winson Chungb1777442015-06-16 13:35:04 -0700288 * Returns whether the specified points are near the scroll bar bounds.
289 */
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);
295 }
296}