blob: 770166daeab91b59dae6e197e47be7a10debf4a7 [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.ObjectAnimator;
Winson Chungb1777442015-06-16 13:35:04 -070019import android.content.res.Resources;
20import android.graphics.Canvas;
21import android.graphics.Color;
22import android.graphics.Paint;
Winson67795952015-08-20 12:23:52 -070023import android.graphics.Path;
Winson Chungb1777442015-06-16 13:35:04 -070024import android.graphics.Rect;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070025import android.util.Property;
Winson Chungb1777442015-06-16 13:35:04 -070026import android.view.MotionEvent;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070027import android.view.View;
Winson Chungb1777442015-06-16 13:35:04 -070028import android.view.ViewConfiguration;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070029import android.widget.TextView;
Sunny Goyalb713ad42015-06-24 11:45:32 -070030
Winson Chungb1777442015-06-16 13:35:04 -070031/**
32 * The track and scrollbar that shows when you scroll the list.
33 */
34public class BaseRecyclerViewFastScrollBar {
35
36 public interface FastScrollFocusableView {
Winsonc0880492015-08-21 11:16:27 -070037 void setFastScrollFocusState(final FastBitmapDrawable.State focusState, boolean animated);
Winson Chungb1777442015-06-16 13:35:04 -070038 }
39
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070040 private static final Property<BaseRecyclerViewFastScrollBar, Integer> TRACK_WIDTH =
41 new Property<BaseRecyclerViewFastScrollBar, Integer>(Integer.class, "width") {
42
43 @Override
44 public Integer get(BaseRecyclerViewFastScrollBar scrollBar) {
45 return scrollBar.mWidth;
46 }
47
48 @Override
49 public void set(BaseRecyclerViewFastScrollBar scrollBar, Integer value) {
50 scrollBar.setTrackWidth(value);
51 }
52 };
53
Winson Chungb1777442015-06-16 13:35:04 -070054 private final static int MAX_TRACK_ALPHA = 30;
55 private final static int SCROLL_BAR_VIS_DURATION = 150;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070056 private static final float FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR = 1.5f;
Winson Chungb1777442015-06-16 13:35:04 -070057
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070058 private final Rect mTmpRect = new Rect();
59 private final BaseRecyclerView mRv;
Winson Chungb1777442015-06-16 13:35:04 -070060
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070061 private final boolean mIsRtl;
Winson Chungb1777442015-06-16 13:35:04 -070062
Winson Chungb1777442015-06-16 13:35:04 -070063 // The inset is the buffer around which a point will still register as a click on the scrollbar
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070064 private final int mTouchInset;
65
66 private final int mMinWidth;
67 private final int mMaxWidth;
68
69 // Current width of the track
70 private int mWidth;
71 private ObjectAnimator mWidthAnimator;
72
73 private final Path mThumbPath = new Path();
74 private final Paint mThumbPaint;
75 private final int mThumbHeight;
76
77 private final Paint mTrackPaint;
78
79 private float mLastTouchY;
Winson Chungb1777442015-06-16 13:35:04 -070080 private boolean mIsDragging;
Winsond2eb49e2015-08-18 17:43:02 -070081 private boolean mIsThumbDetached;
82 private boolean mCanThumbDetach;
Winsonec4845b2015-08-27 10:19:48 -070083 private boolean mIgnoreDragGesture;
Winson Chungb1777442015-06-16 13:35:04 -070084
85 // This is the offset from the top of the scrollbar when the user first starts touching. To
86 // prevent jumping, this offset is applied as the user scrolls.
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070087 private int mTouchOffsetY;
88 private int mThumbOffsetY;
Winson Chungb1777442015-06-16 13:35:04 -070089
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -070090 // Fast scroller popup
91 private TextView mPopupView;
92 private boolean mPopupVisible;
93 private String mPopupSectionName;
Winson Chungb1777442015-06-16 13:35:04 -070094
95 public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
96 mRv = rv;
Winson Chungb1777442015-06-16 13:35:04 -070097 mTrackPaint = new Paint();
98 mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
Winson67795952015-08-20 12:23:52 -070099 mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700100
Winson Chungb1777442015-06-16 13:35:04 -0700101 mThumbPaint = new Paint();
Winson67795952015-08-20 12:23:52 -0700102 mThumbPaint.setAntiAlias(true);
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700103 mThumbPaint.setColor(Utilities.getColorAccent(rv.getContext()));
Winson67795952015-08-20 12:23:52 -0700104 mThumbPaint.setStyle(Paint.Style.FILL);
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700105
106 mWidth = mMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
107 mMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
Winson Chungb1777442015-06-16 13:35:04 -0700108 mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
109 mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700110 mIsRtl = Utilities.isRtl(res);
111 updateThumbPath();
112 }
113
114 public void setPopupView(View popup) {
115 mPopupView = (TextView) popup;
Winson Chungb1777442015-06-16 13:35:04 -0700116 }
117
Winsond2eb49e2015-08-18 17:43:02 -0700118 public void setDetachThumbOnFastScroll() {
119 mCanThumbDetach = true;
120 }
121
122 public void reattachThumbToScroll() {
123 mIsThumbDetached = false;
124 }
125
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700126 private int getDrawLeft() {
127 return mIsRtl ? 0 : (mRv.getWidth() - mMaxWidth);
128 }
129
130 public void setThumbOffsetY(int y) {
131 if (mThumbOffsetY == y) {
Winson Chungb1777442015-06-16 13:35:04 -0700132 return;
133 }
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700134
135 // Invalidate the previous and new thumb area
136 int drawLeft = getDrawLeft();
137 mTmpRect.set(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
138 mThumbOffsetY = y;
139 mTmpRect.union(drawLeft, mThumbOffsetY, drawLeft + mMaxWidth, mThumbOffsetY + mThumbHeight);
140 mRv.invalidate(mTmpRect);
141 }
142
143 public int getThumbOffsetY() {
144 return mThumbOffsetY;
145 }
146
147 private void setTrackWidth(int width) {
148 if (mWidth == width) {
149 return;
150 }
151 int left = getDrawLeft();
152 // Invalidate the whole scroll bar area.
153 mRv.invalidate(left, 0, left + mMaxWidth, mRv.getVisibleHeight());
154
155 mWidth = width;
Winson67795952015-08-20 12:23:52 -0700156 updateThumbPath();
Winson Chungb1777442015-06-16 13:35:04 -0700157 }
158
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700159 /**
160 * Updates the path for the thumb drawable.
161 */
162 private void updateThumbPath() {
163 int smallWidth = mIsRtl ? mWidth : -mWidth;
164 int largeWidth = mIsRtl ? mMaxWidth : -mMaxWidth;
Winsond2eb49e2015-08-18 17:43:02 -0700165
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700166 mThumbPath.reset();
167 mThumbPath.moveTo(0, 0);
168 mThumbPath.lineTo(0, mThumbHeight); // Left edge
169 mThumbPath.lineTo(smallWidth, mThumbHeight); // bottom edge
170 mThumbPath.cubicTo(smallWidth, mThumbHeight, // right edge
171 largeWidth, mThumbHeight / 2,
172 smallWidth, 0);
173 mThumbPath.close();
Winson Chungb1777442015-06-16 13:35:04 -0700174 }
175
176 public int getThumbHeight() {
177 return mThumbHeight;
178 }
179
180 public int getThumbMaxWidth() {
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700181 return mMaxWidth;
Winson Chungb1777442015-06-16 13:35:04 -0700182 }
183
Winsond2eb49e2015-08-18 17:43:02 -0700184 public boolean isDraggingThumb() {
Winson Chungb1777442015-06-16 13:35:04 -0700185 return mIsDragging;
186 }
187
Winsond2eb49e2015-08-18 17:43:02 -0700188 public boolean isThumbDetached() {
189 return mIsThumbDetached;
190 }
191
Winson Chungb1777442015-06-16 13:35:04 -0700192 /**
193 * Handles the touch event and determines whether to show the fast scroller (or updates it if
194 * it is already showing).
195 */
196 public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
197 ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
198
199 int action = ev.getAction();
200 int y = (int) ev.getY();
201 switch (action) {
202 case MotionEvent.ACTION_DOWN:
Winsonec4845b2015-08-27 10:19:48 -0700203 if (isNearThumb(downX, downY)) {
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700204 mTouchOffsetY = downY - mThumbOffsetY;
Winson Chungb1777442015-06-16 13:35:04 -0700205 }
206 break;
207 case MotionEvent.ACTION_MOVE:
Winsonec4845b2015-08-27 10:19:48 -0700208 // Check if we should start scrolling, but ignore this fastscroll gesture if we have
209 // exceeded some fixed movement
210 mIgnoreDragGesture |= Math.abs(y - downY) > config.getScaledPagingTouchSlop();
Winson646c2362015-09-03 11:46:11 -0700211 if (!mIsDragging && !mIgnoreDragGesture && mRv.supportsFastScrolling() &&
212 isNearThumb(downX, lastY) &&
Winson Chungb1777442015-06-16 13:35:04 -0700213 Math.abs(y - downY) > config.getScaledTouchSlop()) {
214 mRv.getParent().requestDisallowInterceptTouchEvent(true);
215 mIsDragging = true;
Winsond2eb49e2015-08-18 17:43:02 -0700216 if (mCanThumbDetach) {
217 mIsThumbDetached = true;
218 }
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700219 mTouchOffsetY += (lastY - downY);
220 animatePopupVisibility(true);
Winsonc0880492015-08-21 11:16:27 -0700221 showActiveScrollbar(true);
Winson Chungb1777442015-06-16 13:35:04 -0700222 }
223 if (mIsDragging) {
224 // Update the fastscroller section name at this touch position
225 int top = mRv.getBackgroundPadding().top;
Hyunyoung Song4ebc3d02016-08-05 10:59:17 -0700226 int bottom = top + mRv.getVisibleHeight() - mThumbHeight;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700227 float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffsetY));
Winson Chungb1777442015-06-16 13:35:04 -0700228 String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
229 (bottom - top));
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700230 if (!sectionName.equals(mPopupSectionName)) {
231 mPopupSectionName = sectionName;
232 mPopupView.setText(sectionName);
233 }
234 animatePopupVisibility(!sectionName.isEmpty());
235 updatePopupY(lastY);
Winsond2eb49e2015-08-18 17:43:02 -0700236 mLastTouchY = boundedY;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700237 setThumbOffsetY((int) mLastTouchY);
Winson Chungb1777442015-06-16 13:35:04 -0700238 }
239 break;
240 case MotionEvent.ACTION_UP:
241 case MotionEvent.ACTION_CANCEL:
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700242 mTouchOffsetY = 0;
Winsond2eb49e2015-08-18 17:43:02 -0700243 mLastTouchY = 0;
Winsonec4845b2015-08-27 10:19:48 -0700244 mIgnoreDragGesture = false;
Winson Chung4c7fc622015-06-24 16:59:31 -0700245 if (mIsDragging) {
246 mIsDragging = false;
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700247 animatePopupVisibility(false);
Winsonc0880492015-08-21 11:16:27 -0700248 showActiveScrollbar(false);
Winson Chung4c7fc622015-06-24 16:59:31 -0700249 }
Winson Chungb1777442015-06-16 13:35:04 -0700250 break;
251 }
252 }
253
254 public void draw(Canvas canvas) {
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700255 if (mThumbOffsetY < 0) {
Winson Chungb1777442015-06-16 13:35:04 -0700256 return;
257 }
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700258 int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
259 if (!mIsRtl) {
260 canvas.translate(mRv.getWidth(), 0);
Winson Chungb1777442015-06-16 13:35:04 -0700261 }
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700262 // Draw the track
263 int thumbWidth = mIsRtl ? mWidth : -mWidth;
264 canvas.drawRect(0, 0, thumbWidth, mRv.getVisibleHeight(), mTrackPaint);
Winson Chungb1777442015-06-16 13:35:04 -0700265
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700266 canvas.translate(0, mThumbOffsetY);
267 canvas.drawPath(mThumbPath, mThumbPaint);
268 canvas.restoreToCount(saveCount);
Winson Chungb1777442015-06-16 13:35:04 -0700269 }
270
271 /**
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700272 * Animates the width of the scrollbar.
Winson Chungb1777442015-06-16 13:35:04 -0700273 */
Winsonc0880492015-08-21 11:16:27 -0700274 private void showActiveScrollbar(boolean isScrolling) {
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700275 if (mWidthAnimator != null) {
276 mWidthAnimator.cancel();
Winson Chungb1777442015-06-16 13:35:04 -0700277 }
Winson67795952015-08-20 12:23:52 -0700278
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700279 mWidthAnimator = ObjectAnimator.ofInt(this, TRACK_WIDTH,
280 isScrolling ? mMaxWidth : mMinWidth);
281 mWidthAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
282 mWidthAnimator.start();
Winson67795952015-08-20 12:23:52 -0700283 }
284
285 /**
Winson Chungb1777442015-06-16 13:35:04 -0700286 * Returns whether the specified points are near the scroll bar bounds.
287 */
Hyunyoung Songf4cbb142016-06-10 12:00:02 -0700288 public boolean isNearThumb(int x, int y) {
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700289 int left = getDrawLeft();
290 mTmpRect.set(left, mThumbOffsetY, left + mMaxWidth, mThumbOffsetY + mThumbHeight);
Winson Chungb1777442015-06-16 13:35:04 -0700291 mTmpRect.inset(mTouchInset, mTouchInset);
292 return mTmpRect.contains(x, y);
293 }
Sunny Goyal5d9fb0e2016-10-08 17:43:48 -0700294
295 private void animatePopupVisibility(boolean visible) {
296 if (mPopupVisible != visible) {
297 mPopupVisible = visible;
298 mPopupView.animate().cancel();
299 mPopupView.animate().alpha(visible ? 1f : 0f).setDuration(visible ? 200 : 150).start();
300 }
301 }
302
303 private void updatePopupY(int lastTouchY) {
304 int height = mPopupView.getHeight();
305 float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height);
306 top = Math.max(mMaxWidth, Math.min(top, mRv.getVisibleHeight() - mMaxWidth - height));
307 mPopupView.setTranslationY(top);
308 }
Winson Chungb1777442015-06-16 13:35:04 -0700309}