blob: 6ff2ec660b7f62798680d94a561e96b3b5c1b267 [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
18import android.animation.Animator;
19import android.animation.AnimatorSet;
20import android.animation.ArgbEvaluator;
21import android.animation.ObjectAnimator;
22import android.animation.ValueAnimator;
23import android.content.res.Resources;
24import android.graphics.Canvas;
25import android.graphics.Color;
26import android.graphics.Paint;
27import android.graphics.Point;
28import android.graphics.Rect;
29import android.view.MotionEvent;
30import android.view.ViewConfiguration;
31
32/**
33 * The track and scrollbar that shows when you scroll the list.
34 */
35public class BaseRecyclerViewFastScrollBar {
36
37 public interface FastScrollFocusableView {
38 void setFastScrollFocused(boolean focused, boolean animated);
39 }
40
41 private final static int MAX_TRACK_ALPHA = 30;
42 private final static int SCROLL_BAR_VIS_DURATION = 150;
43
44 private BaseRecyclerView mRv;
45 private BaseRecyclerViewFastScrollPopup mPopup;
46
47 private AnimatorSet mScrollbarAnimator;
48
49 private int mThumbInactiveColor;
50 private int mThumbActiveColor;
51 private Point mThumbOffset = new Point(-1, -1);
52 private Paint mThumbPaint;
53 private Paint mTrackPaint;
54 private int mThumbMinWidth;
55 private int mThumbMaxWidth;
56 private int mThumbWidth;
57 private int mThumbHeight;
58 // The inset is the buffer around which a point will still register as a click on the scrollbar
59 private int mTouchInset;
60 private boolean mIsDragging;
61
62 // This is the offset from the top of the scrollbar when the user first starts touching. To
63 // prevent jumping, this offset is applied as the user scrolls.
64 private int mTouchOffset;
65
66 private Rect mInvalidateRect = new Rect();
67 private Rect mTmpRect = new Rect();
68
69 public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
70 mRv = rv;
71 mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
72 mTrackPaint = new Paint();
73 mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
74 mTrackPaint.setAlpha(0);
75 mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
76 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
77 mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
78 mThumbPaint = new Paint();
79 mThumbPaint.setColor(mThumbInactiveColor);
80 mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
81 mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
82 mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
83 mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
84 }
85
86 public void setScrollbarThumbOffset(int x, int y) {
87 if (mThumbOffset.x == x && mThumbOffset.y == y) {
88 return;
89 }
90 mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
91 mThumbOffset.set(x, y);
92 mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
93 mRv.getHeight()));
94 mRv.invalidate(mInvalidateRect);
95 }
96
97 // Setter/getter for the search bar width for animations
98 public void setWidth(int width) {
99 mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
100 mThumbWidth = width;
101 mInvalidateRect.union(new Rect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth,
102 mRv.getHeight()));
103 mRv.invalidate(mInvalidateRect);
104 }
105
106 public int getWidth() {
107 return mThumbWidth;
108 }
109
110 // Setter/getter for the track background alpha for animations
111 public void setTrackAlpha(int alpha) {
112 mTrackPaint.setAlpha(alpha);
113 mInvalidateRect.set(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight());
114 mRv.invalidate(mInvalidateRect);
115 }
116
117 public int getTrackAlpha() {
118 return mTrackPaint.getAlpha();
119 }
120
121 public int getThumbHeight() {
122 return mThumbHeight;
123 }
124
125 public int getThumbMaxWidth() {
126 return mThumbMaxWidth;
127 }
128
129 public boolean isDragging() {
130 return mIsDragging;
131 }
132
133 /**
134 * Handles the touch event and determines whether to show the fast scroller (or updates it if
135 * it is already showing).
136 */
137 public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
138 ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
139
140 int action = ev.getAction();
141 int y = (int) ev.getY();
142 switch (action) {
143 case MotionEvent.ACTION_DOWN:
144 if (isNearPoint(downX, downY)) {
145 mTouchOffset = downY - mThumbOffset.y;
146 }
147 break;
148 case MotionEvent.ACTION_MOVE:
149 // Check if we should start scrolling
150 if (!mIsDragging && isNearPoint(downX, downY) &&
151 Math.abs(y - downY) > config.getScaledTouchSlop()) {
152 mRv.getParent().requestDisallowInterceptTouchEvent(true);
153 mIsDragging = true;
154 mTouchOffset += (lastY - downY);
155 mPopup.animateVisibility(true);
156 animateScrollbar(true);
157 }
158 if (mIsDragging) {
159 // Update the fastscroller section name at this touch position
160 int top = mRv.getBackgroundPadding().top;
161 int bottom = mRv.getHeight() - mRv.getBackgroundPadding().bottom - mThumbHeight;
162 float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
163 String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
164 (bottom - top));
165 mPopup.setSectionName(sectionName);
166 mPopup.animateVisibility(!sectionName.isEmpty());
167 mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
168 }
169 break;
170 case MotionEvent.ACTION_UP:
171 case MotionEvent.ACTION_CANCEL:
Winson Chungb1777442015-06-16 13:35:04 -0700172 mTouchOffset = 0;
Winson Chung4c7fc622015-06-24 16:59:31 -0700173 if (mIsDragging) {
174 mIsDragging = false;
175 mPopup.animateVisibility(false);
176 animateScrollbar(false);
177 }
Winson Chungb1777442015-06-16 13:35:04 -0700178 break;
179 }
180 }
181
182 public void draw(Canvas canvas) {
183 if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
184 return;
185 }
186
187 // Draw the scroll bar track and thumb
188 if (mTrackPaint.getAlpha() > 0) {
189 canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
190 }
191 canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
192 mThumbOffset.y + mThumbHeight, mThumbPaint);
193
194 // Draw the popup
195 mPopup.draw(canvas);
196 }
197
198 /**
199 * Animates the width and color of the scrollbar.
200 */
201 private void animateScrollbar(boolean isScrolling) {
202 if (mScrollbarAnimator != null) {
203 mScrollbarAnimator.cancel();
204 }
205 ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
206 isScrolling ? MAX_TRACK_ALPHA : 0);
207 ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
208 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
209 ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
210 mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
211 colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
212 @Override
213 public void onAnimationUpdate(ValueAnimator animator) {
214 mThumbPaint.setColor((Integer) animator.getAnimatedValue());
215 mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
216 mThumbOffset.y + mThumbHeight);
217 }
218 });
219 mScrollbarAnimator = new AnimatorSet();
220 mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
221 mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
222 mScrollbarAnimator.start();
223 }
224
225 /**
226 * Returns whether the specified points are near the scroll bar bounds.
227 */
228 private boolean isNearPoint(int x, int y) {
229 mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
230 mThumbOffset.y + mThumbHeight);
231 mTmpRect.inset(mTouchInset, mTouchInset);
232 return mTmpRect.contains(x, y);
233 }
234}