1 /*
2  * Copyright (C) 2006 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 
17 package android.widget;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.compat.annotation.UnsupportedAppUsage;
22 import android.graphics.Canvas;
23 import android.graphics.ColorFilter;
24 import android.graphics.PixelFormat;
25 import android.graphics.Rect;
26 import android.graphics.drawable.Drawable;
27 import android.os.Build;
28 import android.view.View;
29 
30 import com.android.internal.widget.ScrollBarUtils;
31 
32 /**
33  * This is only used by View for displaying its scroll bars. It should probably
34  * be moved in to the view package since it is used in that lower-level layer.
35  * For now, we'll hide it so it can be cleaned up later.
36  *
37  * {@hide}
38  */
39 public class ScrollBarDrawable extends Drawable implements Drawable.Callback {
40     private Drawable mVerticalTrack;
41     private Drawable mHorizontalTrack;
42     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768422)
43     private Drawable mVerticalThumb;
44     private Drawable mHorizontalThumb;
45 
46     private int mRange;
47     private int mOffset;
48     private int mExtent;
49 
50     private boolean mVertical;
51     private boolean mBoundsChanged;
52     private boolean mRangeChanged;
53     private boolean mAlwaysDrawHorizontalTrack;
54     private boolean mAlwaysDrawVerticalTrack;
55     private boolean mMutated;
56 
57     private int mAlpha = 255;
58     private boolean mHasSetAlpha;
59 
60     private ColorFilter mColorFilter;
61     private boolean mHasSetColorFilter;
62 
63     @UnsupportedAppUsage
ScrollBarDrawable()64     public ScrollBarDrawable() {
65     }
66 
67     /**
68      * Indicate whether the horizontal scrollbar track should always be drawn
69      * regardless of the extent. Defaults to false.
70      *
71      * @param alwaysDrawTrack Whether the track should always be drawn
72      *
73      * @see #getAlwaysDrawHorizontalTrack()
74      */
setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack)75     public void setAlwaysDrawHorizontalTrack(boolean alwaysDrawTrack) {
76         mAlwaysDrawHorizontalTrack = alwaysDrawTrack;
77     }
78 
79     /**
80      * Indicate whether the vertical scrollbar track should always be drawn
81      * regardless of the extent. Defaults to false.
82      *
83      * @param alwaysDrawTrack Whether the track should always be drawn
84      *
85      * @see #getAlwaysDrawVerticalTrack()
86      */
setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack)87     public void setAlwaysDrawVerticalTrack(boolean alwaysDrawTrack) {
88         mAlwaysDrawVerticalTrack = alwaysDrawTrack;
89     }
90 
91     /**
92      * @return whether the vertical scrollbar track should always be drawn
93      *         regardless of the extent.
94      *
95      * @see #setAlwaysDrawVerticalTrack(boolean)
96      */
getAlwaysDrawVerticalTrack()97     public boolean getAlwaysDrawVerticalTrack() {
98         return mAlwaysDrawVerticalTrack;
99     }
100 
101     /**
102      * @return whether the horizontal scrollbar track should always be drawn
103      *         regardless of the extent.
104      *
105      * @see #setAlwaysDrawHorizontalTrack(boolean)
106      */
getAlwaysDrawHorizontalTrack()107     public boolean getAlwaysDrawHorizontalTrack() {
108         return mAlwaysDrawHorizontalTrack;
109     }
110 
setParameters(int range, int offset, int extent, boolean vertical)111     public void setParameters(int range, int offset, int extent, boolean vertical) {
112         if (mVertical != vertical) {
113             mVertical = vertical;
114 
115             mBoundsChanged = true;
116         }
117 
118         if (mRange != range || mOffset != offset || mExtent != extent) {
119             mRange = range;
120             mOffset = offset;
121             mExtent = extent;
122 
123             mRangeChanged = true;
124         }
125     }
126 
127     @Override
draw(Canvas canvas)128     public void draw(Canvas canvas) {
129         final boolean vertical = mVertical;
130         final int extent = mExtent;
131         final int range = mRange;
132 
133         boolean drawTrack = true;
134         boolean drawThumb = true;
135         if (extent <= 0 || range <= extent) {
136             drawTrack = vertical ? mAlwaysDrawVerticalTrack : mAlwaysDrawHorizontalTrack;
137             drawThumb = false;
138         }
139 
140         final Rect r = getBounds();
141         if (canvas.quickReject(r.left, r.top, r.right, r.bottom, Canvas.EdgeType.AA)) {
142             return;
143         }
144 
145         if (drawTrack) {
146             drawTrack(canvas, r, vertical);
147         }
148 
149         if (drawThumb) {
150             final int scrollBarLength = vertical ? r.height() : r.width();
151             final int thickness = vertical ? r.width() : r.height();
152             final int thumbLength =
153                     ScrollBarUtils.getThumbLength(scrollBarLength, thickness, extent, range);
154             final int thumbOffset =
155                     ScrollBarUtils.getThumbOffset(scrollBarLength, thumbLength, extent, range,
156                             mOffset);
157 
158             drawThumb(canvas, r, thumbOffset, thumbLength, vertical);
159         }
160     }
161 
162     @Override
onBoundsChange(Rect bounds)163     protected void onBoundsChange(Rect bounds) {
164         super.onBoundsChange(bounds);
165         mBoundsChanged = true;
166     }
167 
168     @Override
isStateful()169     public boolean isStateful() {
170         return (mVerticalTrack != null && mVerticalTrack.isStateful())
171                 || (mVerticalThumb != null && mVerticalThumb.isStateful())
172                 || (mHorizontalTrack != null && mHorizontalTrack.isStateful())
173                 || (mHorizontalThumb != null && mHorizontalThumb.isStateful())
174                 || super.isStateful();
175     }
176 
177     @Override
onStateChange(int[] state)178     protected boolean onStateChange(int[] state) {
179         boolean changed = super.onStateChange(state);
180         if (mVerticalTrack != null) {
181             changed |= mVerticalTrack.setState(state);
182         }
183         if (mVerticalThumb != null) {
184             changed |= mVerticalThumb.setState(state);
185         }
186         if (mHorizontalTrack != null) {
187             changed |= mHorizontalTrack.setState(state);
188         }
189         if (mHorizontalThumb != null) {
190             changed |= mHorizontalThumb.setState(state);
191         }
192         return changed;
193     }
194 
drawTrack(Canvas canvas, Rect bounds, boolean vertical)195     private void drawTrack(Canvas canvas, Rect bounds, boolean vertical) {
196         final Drawable track;
197         if (vertical) {
198             track = mVerticalTrack;
199         } else {
200             track = mHorizontalTrack;
201         }
202 
203         if (track != null) {
204             if (mBoundsChanged) {
205                 track.setBounds(bounds);
206             }
207             track.draw(canvas);
208         }
209     }
210 
drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical)211     private void drawThumb(Canvas canvas, Rect bounds, int offset, int length, boolean vertical) {
212         final boolean changed = mRangeChanged || mBoundsChanged;
213         if (vertical) {
214             if (mVerticalThumb != null) {
215                 final Drawable thumb = mVerticalThumb;
216                 if (changed) {
217                     thumb.setBounds(bounds.left, bounds.top + offset,
218                             bounds.right, bounds.top + offset + length);
219                 }
220 
221                 thumb.draw(canvas);
222             }
223         } else {
224             if (mHorizontalThumb != null) {
225                 final Drawable thumb = mHorizontalThumb;
226                 if (changed) {
227                     thumb.setBounds(bounds.left + offset, bounds.top,
228                             bounds.left + offset + length, bounds.bottom);
229                 }
230 
231                 thumb.draw(canvas);
232             }
233         }
234     }
235 
236     /**
237      * @see android.view.View#setVerticalThumbDrawable(Drawable)
238      */
239     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
setVerticalThumbDrawable(Drawable thumb)240     public void setVerticalThumbDrawable(Drawable thumb) {
241         if (mVerticalThumb != null) {
242             mVerticalThumb.setCallback(null);
243         }
244 
245         propagateCurrentState(thumb);
246         mVerticalThumb = thumb;
247     }
248 
249     /**
250      * @see View#getVerticalTrackDrawable()
251      */
getVerticalTrackDrawable()252     public @Nullable Drawable getVerticalTrackDrawable() {
253         return mVerticalTrack;
254     }
255 
256     /**
257      * @see View#getVerticalThumbDrawable()
258      */
getVerticalThumbDrawable()259     public @Nullable Drawable getVerticalThumbDrawable() {
260         return mVerticalThumb;
261     }
262 
263     /**
264      * @see View#getHorizontalTrackDrawable()
265      */
getHorizontalTrackDrawable()266     public @Nullable Drawable getHorizontalTrackDrawable() {
267         return mHorizontalTrack;
268     }
269 
270     /**
271      * @see View#getHorizontalThumbDrawable()
272      */
getHorizontalThumbDrawable()273     public @Nullable Drawable getHorizontalThumbDrawable() {
274         return mHorizontalThumb;
275     }
276 
277     /**
278      * @see android.view.View#setVerticalTrackDrawable(Drawable)
279      */
setVerticalTrackDrawable(Drawable track)280     public void setVerticalTrackDrawable(Drawable track) {
281         if (mVerticalTrack != null) {
282             mVerticalTrack.setCallback(null);
283         }
284 
285         propagateCurrentState(track);
286         mVerticalTrack = track;
287     }
288 
289     /**
290      * @see android.view.View#setHorizontalThumbDrawable(Drawable)
291      */
292     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
setHorizontalThumbDrawable(Drawable thumb)293     public void setHorizontalThumbDrawable(Drawable thumb) {
294         if (mHorizontalThumb != null) {
295             mHorizontalThumb.setCallback(null);
296         }
297 
298         propagateCurrentState(thumb);
299         mHorizontalThumb = thumb;
300     }
301 
setHorizontalTrackDrawable(Drawable track)302     public void setHorizontalTrackDrawable(Drawable track) {
303         if (mHorizontalTrack != null) {
304             mHorizontalTrack.setCallback(null);
305         }
306 
307         propagateCurrentState(track);
308         mHorizontalTrack = track;
309     }
310 
propagateCurrentState(Drawable d)311     private void propagateCurrentState(Drawable d) {
312         if (d != null) {
313             if (mMutated) {
314                 d.mutate();
315             }
316 
317             d.setState(getState());
318             d.setCallback(this);
319 
320             if (mHasSetAlpha) {
321                 d.setAlpha(mAlpha);
322             }
323 
324             if (mHasSetColorFilter) {
325                 d.setColorFilter(mColorFilter);
326             }
327         }
328     }
329 
getSize(boolean vertical)330     public int getSize(boolean vertical) {
331         if (vertical) {
332             return mVerticalTrack != null ? mVerticalTrack.getIntrinsicWidth() :
333                     mVerticalThumb != null ? mVerticalThumb.getIntrinsicWidth() : 0;
334         } else {
335             return mHorizontalTrack != null ? mHorizontalTrack.getIntrinsicHeight() :
336                     mHorizontalThumb != null ? mHorizontalThumb.getIntrinsicHeight() : 0;
337         }
338     }
339 
340     @Override
mutate()341     public ScrollBarDrawable mutate() {
342         if (!mMutated && super.mutate() == this) {
343             if (mVerticalTrack != null) {
344                 mVerticalTrack.mutate();
345             }
346             if (mVerticalThumb != null) {
347                 mVerticalThumb.mutate();
348             }
349             if (mHorizontalTrack != null) {
350                 mHorizontalTrack.mutate();
351             }
352             if (mHorizontalThumb != null) {
353                 mHorizontalThumb.mutate();
354             }
355             mMutated = true;
356         }
357         return this;
358     }
359 
360     @Override
setAlpha(int alpha)361     public void setAlpha(int alpha) {
362         mAlpha = alpha;
363         mHasSetAlpha = true;
364 
365         if (mVerticalTrack != null) {
366             mVerticalTrack.setAlpha(alpha);
367         }
368         if (mVerticalThumb != null) {
369             mVerticalThumb.setAlpha(alpha);
370         }
371         if (mHorizontalTrack != null) {
372             mHorizontalTrack.setAlpha(alpha);
373         }
374         if (mHorizontalThumb != null) {
375             mHorizontalThumb.setAlpha(alpha);
376         }
377     }
378 
379     @Override
getAlpha()380     public int getAlpha() {
381         return mAlpha;
382     }
383 
384     @Override
setColorFilter(ColorFilter colorFilter)385     public void setColorFilter(ColorFilter colorFilter) {
386         mColorFilter = colorFilter;
387         mHasSetColorFilter = true;
388 
389         if (mVerticalTrack != null) {
390             mVerticalTrack.setColorFilter(colorFilter);
391         }
392         if (mVerticalThumb != null) {
393             mVerticalThumb.setColorFilter(colorFilter);
394         }
395         if (mHorizontalTrack != null) {
396             mHorizontalTrack.setColorFilter(colorFilter);
397         }
398         if (mHorizontalThumb != null) {
399             mHorizontalThumb.setColorFilter(colorFilter);
400         }
401     }
402 
403     @Override
getColorFilter()404     public ColorFilter getColorFilter() {
405         return mColorFilter;
406     }
407 
408     @Override
getOpacity()409     public int getOpacity() {
410         return PixelFormat.TRANSLUCENT;
411     }
412 
413     @Override
invalidateDrawable(@onNull Drawable who)414     public void invalidateDrawable(@NonNull Drawable who) {
415         invalidateSelf();
416     }
417 
418     @Override
scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)419     public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) {
420         scheduleSelf(what, when);
421     }
422 
423     @Override
unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)424     public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) {
425         unscheduleSelf(what);
426     }
427 
428     @Override
toString()429     public String toString() {
430         return "ScrollBarDrawable: range=" + mRange + " offset=" + mOffset +
431                " extent=" + mExtent + (mVertical ? " V" : " H");
432     }
433 }
434 
435 
436