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