OSDN Git Service

f76aed7ad4fb68d7b29b238f2c6029e19bea53f9
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / BaseRecyclerViewFastScrollBar.java
1 /*
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  */
16 package com.android.launcher3;
17
18 import android.animation.AnimatorSet;
19 import android.animation.ArgbEvaluator;
20 import android.animation.ObjectAnimator;
21 import android.animation.ValueAnimator;
22 import android.content.res.Resources;
23 import android.graphics.Canvas;
24 import android.graphics.Color;
25 import android.graphics.Paint;
26 import android.graphics.Path;
27 import android.graphics.Point;
28 import android.graphics.Rect;
29 import android.view.MotionEvent;
30 import android.view.ViewConfiguration;
31
32 import com.android.launcher3.util.Thunk;
33
34 /**
35  * The track and scrollbar that shows when you scroll the list.
36  */
37 public class BaseRecyclerViewFastScrollBar {
38
39     public interface FastScrollFocusableView {
40         void setFastScrollFocused(boolean focused, boolean animated);
41     }
42
43     private final static int MAX_TRACK_ALPHA = 30;
44     private final static int SCROLL_BAR_VIS_DURATION = 150;
45
46     @Thunk BaseRecyclerView mRv;
47     private BaseRecyclerViewFastScrollPopup mPopup;
48
49     private AnimatorSet mScrollbarAnimator;
50
51     private int mThumbInactiveColor;
52     private int mThumbActiveColor;
53     @Thunk Point mThumbOffset = new Point(-1, -1);
54     @Thunk Paint mThumbPaint;
55     private int mThumbMinWidth;
56     private int mThumbMaxWidth;
57     @Thunk int mThumbWidth;
58     @Thunk int mThumbHeight;
59     private int mThumbCurvature;
60     private Path mThumbPath = new Path();
61     private Paint mTrackPaint;
62     private int mTrackWidth;
63     private float mLastTouchY;
64     // The inset is the buffer around which a point will still register as a click on the scrollbar
65     private int mTouchInset;
66     private boolean mIsDragging;
67     private boolean mIsThumbDetached;
68     private boolean mCanThumbDetach;
69
70     // This is the offset from the top of the scrollbar when the user first starts touching.  To
71     // prevent jumping, this offset is applied as the user scrolls.
72     private int mTouchOffset;
73
74     private Rect mInvalidateRect = new Rect();
75     private Rect mTmpRect = new Rect();
76
77     public BaseRecyclerViewFastScrollBar(BaseRecyclerView rv, Resources res) {
78         mRv = rv;
79         mPopup = new BaseRecyclerViewFastScrollPopup(rv, res);
80         mTrackPaint = new Paint();
81         mTrackPaint.setColor(rv.getFastScrollerTrackColor(Color.BLACK));
82         mTrackPaint.setAlpha(MAX_TRACK_ALPHA);
83         mThumbInactiveColor = rv.getFastScrollerThumbInactiveColor(
84                 res.getColor(R.color.container_fastscroll_thumb_inactive_color));
85         mThumbActiveColor = res.getColor(R.color.container_fastscroll_thumb_active_color);
86         mThumbPaint = new Paint();
87         mThumbPaint.setAntiAlias(true);
88         mThumbPaint.setColor(mThumbInactiveColor);
89         mThumbPaint.setStyle(Paint.Style.FILL);
90         mThumbWidth = mThumbMinWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_min_width);
91         mThumbMaxWidth = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_max_width);
92         mThumbHeight = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_height);
93         mThumbCurvature = mThumbMaxWidth - mThumbMinWidth;
94         mTouchInset = res.getDimensionPixelSize(R.dimen.container_fastscroll_thumb_touch_inset);
95     }
96
97     public void setDetachThumbOnFastScroll() {
98         mCanThumbDetach = true;
99     }
100
101     public void reattachThumbToScroll() {
102         mIsThumbDetached = false;
103     }
104
105     public void setThumbOffset(int x, int y) {
106         if (mThumbOffset.x == x && mThumbOffset.y == y) {
107             return;
108         }
109         mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
110                 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
111         mThumbOffset.set(x, y);
112         updateThumbPath();
113         mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
114                 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
115         mRv.invalidate(mInvalidateRect);
116     }
117
118     public Point getThumbOffset() {
119         return mThumbOffset;
120     }
121
122     // Setter/getter for the thumb bar width for animations
123     public void setThumbWidth(int width) {
124         mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
125                 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
126         mThumbWidth = width;
127         updateThumbPath();
128         mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, mThumbOffset.y,
129                 mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);
130         mRv.invalidate(mInvalidateRect);
131     }
132
133     public int getThumbWidth() {
134         return mThumbWidth;
135     }
136
137     // Setter/getter for the track bar width for animations
138     public void setTrackWidth(int width) {
139         mInvalidateRect.set(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
140                 mRv.getHeight());
141         mTrackWidth = width;
142         updateThumbPath();
143         mInvalidateRect.union(mThumbOffset.x - mThumbCurvature, 0, mThumbOffset.x + mThumbWidth,
144                 mRv.getHeight());
145         mRv.invalidate(mInvalidateRect);
146     }
147
148     public int getTrackWidth() {
149         return mTrackWidth;
150     }
151
152     public int getThumbHeight() {
153         return mThumbHeight;
154     }
155
156     public int getThumbMaxWidth() {
157         return mThumbMaxWidth;
158     }
159
160     public float getLastTouchY() {
161         return mLastTouchY;
162     }
163
164     public boolean isDraggingThumb() {
165         return mIsDragging;
166     }
167
168     public boolean isThumbDetached() {
169         return mIsThumbDetached;
170     }
171
172     /**
173      * Handles the touch event and determines whether to show the fast scroller (or updates it if
174      * it is already showing).
175      */
176     public void handleTouchEvent(MotionEvent ev, int downX, int downY, int lastY) {
177         ViewConfiguration config = ViewConfiguration.get(mRv.getContext());
178
179         int action = ev.getAction();
180         int y = (int) ev.getY();
181         switch (action) {
182             case MotionEvent.ACTION_DOWN:
183                 if (isNearPoint(downX, downY)) {
184                     mTouchOffset = downY - mThumbOffset.y;
185                 }
186                 break;
187             case MotionEvent.ACTION_MOVE:
188                 // Check if we should start scrolling
189                 if (!mIsDragging && isNearPoint(downX, downY) &&
190                         Math.abs(y - downY) > config.getScaledTouchSlop()) {
191                     mRv.getParent().requestDisallowInterceptTouchEvent(true);
192                     mIsDragging = true;
193                     if (mCanThumbDetach) {
194                         mIsThumbDetached = true;
195                     }
196                     mTouchOffset += (lastY - downY);
197                     mPopup.animateVisibility(true);
198                     animateScrollbar(true);
199                 }
200                 if (mIsDragging) {
201                     // Update the fastscroller section name at this touch position
202                     int top = mRv.getBackgroundPadding().top;
203                     int bottom = mRv.getHeight() - mRv.getBackgroundPadding().bottom - mThumbHeight;
204                     float boundedY = (float) Math.max(top, Math.min(bottom, y - mTouchOffset));
205                     String sectionName = mRv.scrollToPositionAtProgress((boundedY - top) /
206                             (bottom - top));
207                     mPopup.setSectionName(sectionName);
208                     mPopup.animateVisibility(!sectionName.isEmpty());
209                     mRv.invalidate(mPopup.updateFastScrollerBounds(mRv, lastY));
210                     mLastTouchY = boundedY;
211                 }
212                 break;
213             case MotionEvent.ACTION_UP:
214             case MotionEvent.ACTION_CANCEL:
215                 mTouchOffset = 0;
216                 mLastTouchY = 0;
217                 if (mIsDragging) {
218                     mIsDragging = false;
219                     mPopup.animateVisibility(false);
220                     animateScrollbar(false);
221                 }
222                 break;
223         }
224     }
225
226     public void draw(Canvas canvas) {
227         if (mThumbOffset.x < 0 || mThumbOffset.y < 0) {
228             return;
229         }
230
231         // Draw the scroll bar track and thumb
232         if (mTrackPaint.getAlpha() > 0) {
233             canvas.drawRect(mThumbOffset.x, 0, mThumbOffset.x + mThumbWidth, mRv.getHeight(), mTrackPaint);
234         }
235         canvas.drawPath(mThumbPath, mThumbPaint);
236
237         // Draw the popup
238         mPopup.draw(canvas);
239     }
240
241     /**
242      * Animates the width and color of the scrollbar.
243      */
244     private void animateScrollbar(boolean isScrolling) {
245         if (mScrollbarAnimator != null) {
246             mScrollbarAnimator.cancel();
247         }
248
249         mScrollbarAnimator = new AnimatorSet();
250         ObjectAnimator trackWidthAnim = ObjectAnimator.ofInt(this, "trackWidth",
251                 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
252         ObjectAnimator thumbWidthAnim = ObjectAnimator.ofInt(this, "thumbWidth",
253                 isScrolling ? mThumbMaxWidth : mThumbMinWidth);
254         mScrollbarAnimator.playTogether(trackWidthAnim, thumbWidthAnim);
255         if (mThumbActiveColor != mThumbInactiveColor) {
256             ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(),
257                     mThumbPaint.getColor(), isScrolling ? mThumbActiveColor : mThumbInactiveColor);
258             colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
259                 @Override
260                 public void onAnimationUpdate(ValueAnimator animator) {
261                     mThumbPaint.setColor((Integer) animator.getAnimatedValue());
262                     mRv.invalidate(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
263                             mThumbOffset.y + mThumbHeight);
264                 }
265             });
266             mScrollbarAnimator.play(colorAnimation);
267         }
268         mScrollbarAnimator.setDuration(SCROLL_BAR_VIS_DURATION);
269         mScrollbarAnimator.start();
270     }
271
272     /**
273      * Updates the path for the thumb drawable.
274      */
275     private void updateThumbPath() {
276         mThumbCurvature = mThumbMaxWidth - mThumbWidth;
277         mThumbPath.reset();
278         mThumbPath.moveTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y);                    // tr
279         mThumbPath.lineTo(mThumbOffset.x + mThumbWidth, mThumbOffset.y + mThumbHeight);     // br
280         mThumbPath.lineTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight);                   // bl
281         mThumbPath.cubicTo(mThumbOffset.x, mThumbOffset.y + mThumbHeight,
282                 mThumbOffset.x - mThumbCurvature, mThumbOffset.y + mThumbHeight / 2,
283                 mThumbOffset.x, mThumbOffset.y);                                            // bl2tl
284         mThumbPath.close();
285     }
286
287     /**
288      * Returns whether the specified points are near the scroll bar bounds.
289      */
290     private boolean isNearPoint(int x, int y) {
291         mTmpRect.set(mThumbOffset.x, mThumbOffset.y, mThumbOffset.x + mThumbWidth,
292                 mThumbOffset.y + mThumbHeight);
293         mTmpRect.inset(mTouchInset, mTouchInset);
294         return mTmpRect.contains(x, y);
295     }
296 }