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