blob: 2c4184dc48d74867cac430dc0846eb32cfaa6e58 [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;
Winson Chungb1777442015-06-16 13:35:04 -070059 // 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:
Winson Chungb1777442015-06-16 13:35:04 -0700173 mTouchOffset = 0;
Winson Chung4c7fc622015-06-24 16:59:31 -0700174 if (mIsDragging) {
175 mIsDragging = false;
176 mPopup.animateVisibility(false);
177 animateScrollbar(false);
178 }
Winson Chungb1777442015-06-16 13:35:04 -0700179 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}