blob: 29e234d6cbea1952366a1d84ffdaf052589728f2 [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;
26import android.graphics.Point;
27import android.graphics.Rect;
28import android.view.MotionEvent;
29import android.view.ViewConfiguration;
30
Sunny Goyalb713ad42015-06-24 11:45:32 -070031import com.android.launcher3.util.Thunk;
32
Winson Chungb1777442015-06-16 13:35:04 -070033/**
34 * The track and scrollbar that shows when you scroll the list.
35 */
36public 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
Sunny Goyalb713ad42015-06-24 11:45:32 -070045 @Thunk BaseRecyclerView mRv;
Winson Chungb1777442015-06-16 13:35:04 -070046 private BaseRecyclerViewFastScrollPopup mPopup;
47
48 private AnimatorSet mScrollbarAnimator;
49
50 private int mThumbInactiveColor;
51 private int mThumbActiveColor;
Sunny Goyalb713ad42015-06-24 11:45:32 -070052 @Thunk Point mThumbOffset = new Point(-1, -1);
53 @Thunk Paint mThumbPaint;
Winson Chungb1777442015-06-16 13:35:04 -070054 private Paint mTrackPaint;
55 private int mThumbMinWidth;
56 private int mThumbMaxWidth;
Sunny Goyalb713ad42015-06-24 11:45:32 -070057 @Thunk int mThumbWidth;
58 @Thunk int mThumbHeight;
Winsond2eb49e2015-08-18 17:43:02 -070059 private float mLastTouchY;
Winson Chungb1777442015-06-16 13:35:04 -070060 // The inset is the buffer around which a point will still register as a click on the scrollbar
61 private int mTouchInset;
62 private boolean mIsDragging;
Winsond2eb49e2015-08-18 17:43:02 -070063 private boolean mIsThumbDetached;
64 private boolean mCanThumbDetach;
Winson Chungb1777442015-06-16 13:35:04 -070065
66 // This is the offset from the top of the scrollbar when the user first starts touching. To
67 // prevent jumping, this offset is applied as the user scrolls.
68 private int mTouchOffset;
69
70 private Rect mInvalidateRect = new Rect();
71 private Rect mTmpRect = new Rect();
72
73 public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
74 mRv = rv;
75 mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
76 mTrackPaint = new Paint();
77 mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
78 mTrackPaint.setAlpha(0);
79 mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
80 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
81 mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
82 mThumbPaint = new Paint();
83 mThumbPaint.setColor(mThumbInactiveColor);
84 mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
85 mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
86 mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
87 mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
88 }
89
Winsond2eb49e2015-08-18 17:43:02 -070090 public void setDetachThumbOnFastScroll() {
91 mCanThumbDetach = true;
92 }
93
94 public void reattachThumbToScroll() {
95 mIsThumbDetached = false;
96 }
97
98 public void setThumbOffset(int x, int y) {
Winson Chungb1777442015-06-16 13:35:04 -070099 if (mThumbOffset.x == x && mThumbOffset.y == y) {
100 return;
101 }
102 mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
103 mThumbOffset.set(x, y);
104 mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
105 mRv.getHeight()));
106 mRv.invalidate(mInvalidateRect);
107 }
108
Winsond2eb49e2015-08-18 17:43:02 -0700109 public Point getThumbOffset() {
110 return mThumbOffset;
111 }
112
Winson Chungb1777442015-06-16 13:35:04 -0700113 // Setter/getter for the search bar width for animations
Winsond2eb49e2015-08-18 17:43:02 -0700114 public void setThumbWidth(int width) {
Winson Chungb1777442015-06-16 13:35:04 -0700115 mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
116 mThumbWidth = width;
117 mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
118 mRv.getHeight()));
119 mRv.invalidate(mInvalidateRect);
120 }
121
Winsond2eb49e2015-08-18 17:43:02 -0700122 public int getThumbWidth() {
Winson Chungb1777442015-06-16 13:35:04 -0700123 return mThumbWidth;
124 }
125
126 // Setter/getter for the track background alpha for animations
127 public void setTrackAlpha(int alpha) {
128 mTrackPaint.setAlpha(alpha);
129 mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
130 mRv.invalidate(mInvalidateRect);
131 }
132
133 public int getTrackAlpha() {
134 return mTrackPaint.getAlpha();
135 }
136
137 public int getThumbHeight() {
138 return mThumbHeight;
139 }
140
141 public int getThumbMaxWidth() {
142 return mThumbMaxWidth;
143 }
144
Winsond2eb49e2015-08-18 17:43:02 -0700145 public float getLastTouchY() {
146 return mLastTouchY;
147 }
148
149 public boolean isDraggingThumb() {
Winson Chungb1777442015-06-16 13:35:04 -0700150 return mIsDragging;
151 }
152
Winsond2eb49e2015-08-18 17:43:02 -0700153 public boolean isThumbDetached() {
154 return mIsThumbDetached;
155 }
156
Winson Chungb1777442015-06-16 13:35:04 -0700157 /**
158 * Handles the touch event and determines whether to show the fast scroller (or updates it if
159 * it is already showing).
160 */
161 public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
162 ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
163
164 int action = ev.getAction();
165 int y = (int) ev.getY();
166 switch (action) {
167 case MotionEvent.ACTION_DOWN:
168 if (isNearPoint(downX, downY)) {
169 mTouchOffset = downY - mThumbOffset.y;
170 }
171 break;
172 case MotionEvent.ACTION_MOVE:
173 // Check if we should start scrolling
174 if (!mIsDragging && isNearPoint(downX, downY) &&
175 Math.abs(y - downY) > config.getScaledTouchSlop()) {
176 mRv.getParent().requestDisallowInterceptTouchEvent(true);
177 mIsDragging = true;
Winsond2eb49e2015-08-18 17:43:02 -0700178 if (mCanThumbDetach) {
179 mIsThumbDetached = true;
180 }
Winson Chungb1777442015-06-16 13:35:04 -0700181 mTouchOffset += (lastY - downY);
182 mPopup.animateVisibility(true);
183 animateScrollbar(true);
184 }
185 if (mIsDragging) {
186 // Update the fastscroller section name at this touch position
187 int top = mRv.getBackgroundPadding().top;
188 int bottom = mRv.getHeight() - mRv.getBackgroundPadding().bottom - mThumbHeight;
189 float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
190 String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
191 (bottom - top));
192 mPopup.setSectionName(sectionName);
193 mPopup.animateVisibility(!sectionName.isEmpty());
194 mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
Winsond2eb49e2015-08-18 17:43:02 -0700195 mLastTouchY = boundedY;
Winson Chungb1777442015-06-16 13:35:04 -0700196 }
197 break;
198 case MotionEvent.ACTION_UP:
199 case MotionEvent.ACTION_CANCEL:
Winson Chungb1777442015-06-16 13:35:04 -0700200 mTouchOffset = 0;
Winsond2eb49e2015-08-18 17:43:02 -0700201 mLastTouchY = 0;
Winson Chung4c7fc622015-06-24 16:59:31 -0700202 if (mIsDragging) {
203 mIsDragging = false;
204 mPopup.animateVisibility(false);
205 animateScrollbar(false);
206 }
Winson Chungb1777442015-06-16 13:35:04 -0700207 break;
208 }
209 }
210
211 public void draw(Canvas canvas) {
212 if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
213 return;
214 }
215
216 // Draw the scroll bar track and thumb
217 if (mTrackPaint.getAlpha() > 0) {
218 canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
219 }
220 canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
221 mThumbOffset.y + mThumbHeight, mThumbPaint);
222
223 // Draw the popup
224 mPopup.draw(canvas);
225 }
226
227 /**
228 * Animates the width and color of the scrollbar.
229 */
230 private void animateScrollbar(boolean isScrolling) {
231 if (mScrollbarAnimator != null) {
232 mScrollbarAnimator.cancel();
233 }
234 ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
235 isScrolling ? MAX_TRACK_ALPHA : 0);
Winsond2eb49e2015-08-18 17:43:02 -0700236 ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
Winson Chungb1777442015-06-16 13:35:04 -0700237 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
238 ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
239 mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
240 colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
241 @Override
242 public void onAnimationUpdate(ValueAnimator animator) {
243 mThumbPaint.setColor((Integer) animator.getAnimatedValue());
244 mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
245 mThumbOffset.y + mThumbHeight);
246 }
247 });
248 mScrollbarAnimator = new AnimatorSet();
249 mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
250 mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
251 mScrollbarAnimator.start();
252 }
253
254 /**
255 * Returns whether the specified points are near the scroll bar bounds.
256 */
257 private boolean isNearPoint(int x, int y) {
258 mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
259 mThumbOffset.y + mThumbHeight);
260 mTmpRect.inset(mTouchInset, mTouchInset);
261 return mTmpRect.contains(x, y);
262 }
263}