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