blob: 96e994b0526ce0249cd087993a88e5f54602bce8 [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:
172 mIsDragging = false;
173 mTouchOffset = 0;
174 mPopup.animateVisibility(false);
175 animateScrollbar(false);
176 break;
177 }
178 }
179
180 public void draw(Canvas canvas) {
181 if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
182 return;
183 }
184
185 // Draw the scroll bar track and thumb
186 if (mTrackPaint.getAlpha() > 0) {
187 canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
188 }
189 canvas.drawRect(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
190 mThumbOffset.y + mThumbHeight, mThumbPaint);
191
192 // Draw the popup
193 mPopup.draw(canvas);
194 }
195
196 /**
197 * Animates the width and color of the scrollbar.
198 */
199 private void animateScrollbar(boolean isScrolling) {
200 if (mScrollbarAnimator != null) {
201 mScrollbarAnimator.cancel();
202 }
203 ObjectAnimator trackAlphaAnim = ObjectAnimator.ofInt(this, "trackAlpha",
204 isScrolling ? MAX_TRACK_ALPHA : 0);
205 ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "width",
206 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
207 ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
208 mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
209 colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
210 @Override
211 public void onAnimationUpdate(ValueAnimator animator) {
212 mThumbPaint.setColor((Integer) animator.getAnimatedValue());
213 mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
214 mThumbOffset.y + mThumbHeight);
215 }
216 });
217 mScrollbarAnimator = new AnimatorSet();
218 mScrollbarAnimator.playTogether(trackAlphaAnim, thumbWidthAnim, colorAnimation);
219 mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
220 mScrollbarAnimator.start();
221 }
222
223 /**
224 * Returns whether the specified points are near the scroll bar bounds.
225 */
226 private boolean isNearPoint(int x, int y) {
227 mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
228 mThumbOffset.y + mThumbHeight);
229 mTmpRect.inset(mTouchInset, mTouchInset);
230 return mTmpRect.contains(x, y);
231 }
232}