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.graphics.drawable;
18
19import android.content.res.Resources;
20import android.content.res.TypedArray;
21import android.graphics.Canvas;
22import android.graphics.ColorFilter;
23import android.graphics.DashPathEffect;
24import android.graphics.LinearGradient;
25import android.graphics.Paint;
26import android.graphics.PixelFormat;
27import android.graphics.Rect;
28import android.graphics.RectF;
29import android.graphics.Shader;
30import android.graphics.Path;
31import android.graphics.RadialGradient;
32import android.graphics.SweepGradient;
33import android.util.AttributeSet;
34import android.util.Log;
35import android.util.TypedValue;
36
37import org.xmlpull.v1.XmlPullParser;
38import org.xmlpull.v1.XmlPullParserException;
39
40import java.io.IOException;
41
42/**
43 * A Drawable with a color gradient for buttons, backgrounds, etc.
44 *
45 * <p>It can be defined in an XML file with the <code>&lt;shape></code> element. For more
46 * information, see the guide to <a
47 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
48 *
49 * @attr ref android.R.styleable#GradientDrawable_visible
50 * @attr ref android.R.styleable#GradientDrawable_shape
51 * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio
52 * @attr ref android.R.styleable#GradientDrawable_innerRadius
53 * @attr ref android.R.styleable#GradientDrawable_thicknessRatio
54 * @attr ref android.R.styleable#GradientDrawable_thickness
55 * @attr ref android.R.styleable#GradientDrawable_useLevel
56 * @attr ref android.R.styleable#GradientDrawableSize_width
57 * @attr ref android.R.styleable#GradientDrawableSize_height
58 * @attr ref android.R.styleable#GradientDrawableGradient_startColor
59 * @attr ref android.R.styleable#GradientDrawableGradient_centerColor
60 * @attr ref android.R.styleable#GradientDrawableGradient_endColor
61 * @attr ref android.R.styleable#GradientDrawableGradient_useLevel
62 * @attr ref android.R.styleable#GradientDrawableGradient_angle
63 * @attr ref android.R.styleable#GradientDrawableGradient_type
64 * @attr ref android.R.styleable#GradientDrawableGradient_centerX
65 * @attr ref android.R.styleable#GradientDrawableGradient_centerY
66 * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius
67 * @attr ref android.R.styleable#GradientDrawableSolid_color
68 * @attr ref android.R.styleable#GradientDrawableStroke_width
69 * @attr ref android.R.styleable#GradientDrawableStroke_color
70 * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth
71 * @attr ref android.R.styleable#GradientDrawableStroke_dashGap
72 * @attr ref android.R.styleable#GradientDrawablePadding_left
73 * @attr ref android.R.styleable#GradientDrawablePadding_top
74 * @attr ref android.R.styleable#GradientDrawablePadding_right
75 * @attr ref android.R.styleable#GradientDrawablePadding_bottom
76 */
77public class GradientDrawable extends Drawable {
78    /**
79     * Shape is a rectangle, possibly with rounded corners
80     */
81    public static final int RECTANGLE = 0;
82
83    /**
84     * Shape is an ellipse
85     */
86    public static final int OVAL = 1;
87
88    /**
89     * Shape is a line
90     */
91    public static final int LINE = 2;
92
93    /**
94     * Shape is a ring.
95     */
96    public static final int RING = 3;
97
98    /**
99     * Gradient is linear (default.)
100     */
101    public static final int LINEAR_GRADIENT = 0;
102
103    /**
104     * Gradient is circular.
105     */
106    public static final int RADIAL_GRADIENT = 1;
107
108    /**
109     * Gradient is a sweep.
110     */
111    public static final int SWEEP_GRADIENT  = 2;
112
113    private GradientState mGradientState;
114
115    private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
116    private Rect mPadding;
117    private Paint mStrokePaint;   // optional, set by the caller
118    private ColorFilter mColorFilter;   // optional, set by the caller
119    private int mAlpha = 0xFF;  // modified by the caller
120    private boolean mDither;
121
122    private final Path mPath = new Path();
123    private final RectF mRect = new RectF();
124
125    private Paint mLayerPaint;    // internal, used if we use saveLayer()
126    private boolean mRectIsDirty;   // internal state
127    private boolean mMutated;
128    private Path mRingPath;
129    private boolean mPathIsDirty = true;
130
131    /**
132     * Controls how the gradient is oriented relative to the drawable's bounds
133     */
134    public enum Orientation {
135        /** draw the gradient from the top to the bottom */
136        TOP_BOTTOM,
137        /** draw the gradient from the top-right to the bottom-left */
138        TR_BL,
139        /** draw the gradient from the right to the left */
140        RIGHT_LEFT,
141        /** draw the gradient from the bottom-right to the top-left */
142        BR_TL,
143        /** draw the gradient from the bottom to the top */
144        BOTTOM_TOP,
145        /** draw the gradient from the bottom-left to the top-right */
146        BL_TR,
147        /** draw the gradient from the left to the right */
148        LEFT_RIGHT,
149        /** draw the gradient from the top-left to the bottom-right */
150        TL_BR,
151    }
152
153    public GradientDrawable() {
154        this(new GradientState(Orientation.TOP_BOTTOM, null));
155    }
156
157    /**
158     * Create a new gradient drawable given an orientation and an array
159     * of colors for the gradient.
160     */
161    public GradientDrawable(Orientation orientation, int[] colors) {
162        this(new GradientState(orientation, colors));
163    }
164
165    @Override
166    public boolean getPadding(Rect padding) {
167        if (mPadding != null) {
168            padding.set(mPadding);
169            return true;
170        } else {
171            return super.getPadding(padding);
172        }
173    }
174
175    /**
176     * <p>Specify radii for each of the 4 corners. For each corner, the array
177     * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are ordered
178     * top-left, top-right, bottom-right, bottom-left. This property
179     * is honored only when the shape is of type {@link #RECTANGLE}.</p>
180     * <p><strong>Note</strong>: changing this property will affect all instances
181     * of a drawable loaded from a resource. It is recommended to invoke
182     * {@link #mutate()} before changing this property.</p>
183     *
184     * @param radii 4 pairs of X and Y radius for each corner, specified in pixels.
185     *              The length of this array must be >= 8
186     *
187     * @see #mutate()
188     * @see #setCornerRadii(float[])
189     * @see #setShape(int)
190     */
191    public void setCornerRadii(float[] radii) {
192        mGradientState.setCornerRadii(radii);
193        mPathIsDirty = true;
194        invalidateSelf();
195    }
196
197    /**
198     * <p>Specify radius for the corners of the gradient. If this is > 0, then the
199     * drawable is drawn in a round-rectangle, rather than a rectangle. This property
200     * is honored only when the shape is of type {@link #RECTANGLE}.</p>
201     * <p><strong>Note</strong>: changing this property will affect all instances
202     * of a drawable loaded from a resource. It is recommended to invoke
203     * {@link #mutate()} before changing this property.</p>
204     *
205     * @param radius The radius in pixels of the corners of the rectangle shape
206     *
207     * @see #mutate()
208     * @see #setCornerRadii(float[])
209     * @see #setShape(int)
210     */
211    public void setCornerRadius(float radius) {
212        mGradientState.setCornerRadius(radius);
213        mPathIsDirty = true;
214        invalidateSelf();
215    }
216
217    /**
218     * <p>Set the stroke width and color for the drawable. If width is zero,
219     * then no stroke is drawn.</p>
220     * <p><strong>Note</strong>: changing this property will affect all instances
221     * of a drawable loaded from a resource. It is recommended to invoke
222     * {@link #mutate()} before changing this property.</p>
223     *
224     * @param width The width in pixels of the stroke
225     * @param color The color of the stroke
226     *
227     * @see #mutate()
228     * @see #setStroke(int, int, float, float)
229     */
230    public void setStroke(int width, int color) {
231        setStroke(width, color, 0, 0);
232    }
233
234    /**
235     * <p>Set the stroke width and color for the drawable. If width is zero,
236     * then no stroke is drawn. This method can also be used to dash the stroke.</p>
237     * <p><strong>Note</strong>: changing this property will affect all instances
238     * of a drawable loaded from a resource. It is recommended to invoke
239     * {@link #mutate()} before changing this property.</p>
240     *
241     * @param width The width in pixels of the stroke
242     * @param color The color of the stroke
243     * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes
244     * @param dashGap The gap in pixels between dashes
245     *
246     * @see #mutate()
247     * @see #setStroke(int, int)
248     */
249    public void setStroke(int width, int color, float dashWidth, float dashGap) {
250        mGradientState.setStroke(width, color, dashWidth, dashGap);
251
252        if (mStrokePaint == null)  {
253            mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
254            mStrokePaint.setStyle(Paint.Style.STROKE);
255        }
256        mStrokePaint.setStrokeWidth(width);
257        mStrokePaint.setColor(color);
258
259        DashPathEffect e = null;
260        if (dashWidth > 0) {
261            e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0);
262        }
263        mStrokePaint.setPathEffect(e);
264        invalidateSelf();
265    }
266
267
268    /**
269     * <p>Sets the size of the shape drawn by this drawable.</p>
270     * <p><strong>Note</strong>: changing this property will affect all instances
271     * of a drawable loaded from a resource. It is recommended to invoke
272     * {@link #mutate()} before changing this property.</p>
273     *
274     * @param width The width of the shape used by this drawable
275     * @param height The height of the shape used by this drawable
276     *
277     * @see #mutate()
278     * @see #setGradientType(int)
279     */
280    public void setSize(int width, int height) {
281        mGradientState.setSize(width, height);
282        mPathIsDirty = true;
283        invalidateSelf();
284    }
285
286    /**
287     * <p>Sets the type of shape used to draw the gradient.</p>
288     * <p><strong>Note</strong>: changing this property will affect all instances
289     * of a drawable loaded from a resource. It is recommended to invoke
290     * {@link #mutate()} before changing this property.</p>
291     *
292     * @param shape The desired shape for this drawable: {@link #LINE},
293     *              {@link #OVAL}, {@link #RECTANGLE} or {@link #RING}
294     *
295     * @see #mutate()
296     */
297    public void setShape(int shape) {
298        mRingPath = null;
299        mPathIsDirty = true;
300        mGradientState.setShape(shape);
301        invalidateSelf();
302    }
303
304    /**
305     * <p>Sets the type of gradient used by this drawable..</p>
306     * <p><strong>Note</strong>: changing this property will affect all instances
307     * of a drawable loaded from a resource. It is recommended to invoke
308     * {@link #mutate()} before changing this property.</p>
309     *
310     * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT},
311     *                 {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}
312     *
313     * @see #mutate()
314     */
315    public void setGradientType(int gradient) {
316        mGradientState.setGradientType(gradient);
317        mRectIsDirty = true;
318        invalidateSelf();
319    }
320
321    /**
322     * <p>Sets the center location of the gradient. The radius is honored only when
323     * the gradient type is set to {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}.</p>
324     * <p><strong>Note</strong>: changing this property will affect all instances
325     * of a drawable loaded from a resource. It is recommended to invoke
326     * {@link #mutate()} before changing this property.</p>
327     *
328     * @param x The x coordinate of the gradient's center
329     * @param y The y coordinate of the gradient's center
330     *
331     * @see #mutate()
332     * @see #setGradientType(int)
333     */
334    public void setGradientCenter(float x, float y) {
335        mGradientState.setGradientCenter(x, y);
336        mRectIsDirty = true;
337        invalidateSelf();
338    }
339
340    /**
341     * <p>Sets the radius of the gradient. The radius is honored only when the
342     * gradient type is set to {@link #RADIAL_GRADIENT}.</p>
343     * <p><strong>Note</strong>: changing this property will affect all instances
344     * of a drawable loaded from a resource. It is recommended to invoke
345     * {@link #mutate()} before changing this property.</p>
346     *
347     * @param gradientRadius The radius of the gradient in pixels
348     *
349     * @see #mutate()
350     * @see #setGradientType(int)
351     */
352    public void setGradientRadius(float gradientRadius) {
353        mGradientState.setGradientRadius(gradientRadius);
354        mRectIsDirty = true;
355        invalidateSelf();
356    }
357
358    /**
359     * <p>Sets whether or not this drawable will honor its <code>level</code>
360     * property.</p>
361     * <p><strong>Note</strong>: changing this property will affect all instances
362     * of a drawable loaded from a resource. It is recommended to invoke
363     * {@link #mutate()} before changing this property.</p>
364     *
365     * @param useLevel True if this drawable should honor its level, false otherwise
366     *
367     * @see #mutate()
368     * @see #setLevel(int)
369     * @see #getLevel()
370     */
371    public void setUseLevel(boolean useLevel) {
372        mGradientState.mUseLevel = useLevel;
373        mRectIsDirty = true;
374        invalidateSelf();
375    }
376
377    private int modulateAlpha(int alpha) {
378        int scale = mAlpha + (mAlpha >> 7);
379        return alpha * scale >> 8;
380    }
381
382    /**
383     * Returns the orientation of the gradient defined in this drawable.
384     */
385    public Orientation getOrientation() {
386        return mGradientState.mOrientation;
387    }
388
389    /**
390     * <p>Changes the orientation of the gradient defined in this drawable.</p>
391     * <p><strong>Note</strong>: changing orientation will affect all instances
392     * of a drawable loaded from a resource. It is recommended to invoke
393     * {@link #mutate()} before changing the orientation.</p>
394     *
395     * @param orientation The desired orientation (angle) of the gradient
396     *
397     * @see #mutate()
398     */
399    public void setOrientation(Orientation orientation) {
400        mGradientState.mOrientation = orientation;
401        mRectIsDirty = true;
402        invalidateSelf();
403    }
404
405    /**
406     * <p>Sets the colors used to draw the gradient. Each color is specified as an
407     * ARGB integer and the array must contain at least 2 colors.</p>
408     * <p><strong>Note</strong>: changing orientation will affect all instances
409     * of a drawable loaded from a resource. It is recommended to invoke
410     * {@link #mutate()} before changing the orientation.</p>
411     *
412     * @param colors 2 or more ARGB colors
413     *
414     * @see #mutate()
415     * @see #setColor(int)
416     */
417    public void setColors(int[] colors) {
418        mGradientState.setColors(colors);
419        mRectIsDirty = true;
420        invalidateSelf();
421    }
422
423    @Override
424    public void draw(Canvas canvas) {
425        if (!ensureValidRect()) {
426            // nothing to draw
427            return;
428        }
429
430        // remember the alpha values, in case we temporarily overwrite them
431        // when we modulate them with mAlpha
432        final int prevFillAlpha = mFillPaint.getAlpha();
433        final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0;
434        // compute the modulate alpha values
435        final int currFillAlpha = modulateAlpha(prevFillAlpha);
436        final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha);
437
438        final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null &&
439                mStrokePaint.getStrokeWidth() > 0;
440        final boolean haveFill = currFillAlpha > 0;
441        final GradientState st = mGradientState;
442        /*  we need a layer iff we're drawing both a fill and stroke, and the
443            stroke is non-opaque, and our shapetype actually supports
444            fill+stroke. Otherwise we can just draw the stroke (if any) on top
445            of the fill (if any) without worrying about blending artifacts.
446         */
447         final boolean useLayer = haveStroke && haveFill && st.mShape != LINE &&
448                 currStrokeAlpha < 255 && (mAlpha < 255 || mColorFilter != null);
449
450        /*  Drawing with a layer is slower than direct drawing, but it
451            allows us to apply paint effects like alpha and colorfilter to
452            the result of multiple separate draws. In our case, if the user
453            asks for a non-opaque alpha value (via setAlpha), and we're
454            stroking, then we need to apply the alpha AFTER we've drawn
455            both the fill and the stroke.
456        */
457        if (useLayer) {
458            if (mLayerPaint == null) {
459                mLayerPaint = new Paint();
460            }
461            mLayerPaint.setDither(mDither);
462            mLayerPaint.setAlpha(mAlpha);
463            mLayerPaint.setColorFilter(mColorFilter);
464
465            float rad = mStrokePaint.getStrokeWidth();
466            canvas.saveLayer(mRect.left - rad, mRect.top - rad,
467                             mRect.right + rad, mRect.bottom + rad,
468                             mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG);
469
470            // don't perform the filter in our individual paints
471            // since the layer will do it for us
472            mFillPaint.setColorFilter(null);
473            mStrokePaint.setColorFilter(null);
474        } else {
475            /*  if we're not using a layer, apply the dither/filter to our
476                individual paints
477            */
478            mFillPaint.setAlpha(currFillAlpha);
479            mFillPaint.setDither(mDither);
480            mFillPaint.setColorFilter(mColorFilter);
481            if (mColorFilter != null && !mGradientState.mHasSolidColor) {
482                mFillPaint.setColor(mAlpha << 24);
483            }
484            if (haveStroke) {
485                mStrokePaint.setAlpha(currStrokeAlpha);
486                mStrokePaint.setDither(mDither);
487                mStrokePaint.setColorFilter(mColorFilter);
488            }
489        }
490
491        switch (st.mShape) {
492            case RECTANGLE:
493                if (st.mRadiusArray != null) {
494                    if (mPathIsDirty || mRectIsDirty) {
495                        mPath.reset();
496                        mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
497                        mPathIsDirty = mRectIsDirty = false;
498                    }
499                    canvas.drawPath(mPath, mFillPaint);
500                    if (haveStroke) {
501                        canvas.drawPath(mPath, mStrokePaint);
502                    }
503                } else if (st.mRadius > 0.0f) {
504                    // since the caller is only giving us 1 value, we will force
505                    // it to be square if the rect is too small in one dimension
506                    // to show it. If we did nothing, Skia would clamp the rad
507                    // independently along each axis, giving us a thin ellipse
508                    // if the rect were very wide but not very tall
509                    float rad = st.mRadius;
510                    float r = Math.min(mRect.width(), mRect.height()) * 0.5f;
511                    if (rad > r) {
512                        rad = r;
513                    }
514                    canvas.drawRoundRect(mRect, rad, rad, mFillPaint);
515                    if (haveStroke) {
516                        canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
517                    }
518                } else {
519                    if (mFillPaint.getColor() != 0 || mColorFilter != null ||
520                            mFillPaint.getShader() != null) {
521                        canvas.drawRect(mRect, mFillPaint);
522                    }
523                    if (haveStroke) {
524                        canvas.drawRect(mRect, mStrokePaint);
525                    }
526                }
527                break;
528            case OVAL:
529                canvas.drawOval(mRect, mFillPaint);
530                if (haveStroke) {
531                    canvas.drawOval(mRect, mStrokePaint);
532                }
533                break;
534            case LINE: {
535                RectF r = mRect;
536                float y = r.centerY();
537                canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
538                break;
539            }
540            case RING:
541                Path path = buildRing(st);
542                canvas.drawPath(path, mFillPaint);
543                if (haveStroke) {
544                    canvas.drawPath(path, mStrokePaint);
545                }
546                break;
547        }
548
549        if (useLayer) {
550            canvas.restore();
551        } else {
552            mFillPaint.setAlpha(prevFillAlpha);
553            if (haveStroke) {
554                mStrokePaint.setAlpha(prevStrokeAlpha);
555            }
556        }
557    }
558
559    private Path buildRing(GradientState st) {
560        if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
561        mPathIsDirty = false;
562
563        float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f;
564
565        RectF bounds = new RectF(mRect);
566
567        float x = bounds.width() / 2.0f;
568        float y = bounds.height() / 2.0f;
569
570        float thickness = st.mThickness != -1 ?
571                st.mThickness : bounds.width() / st.mThicknessRatio;
572        // inner radius
573        float radius = st.mInnerRadius != -1 ?
574                st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio;
575
576        RectF innerBounds = new RectF(bounds);
577        innerBounds.inset(x - radius, y - radius);
578
579        bounds = new RectF(innerBounds);
580        bounds.inset(-thickness, -thickness);
581
582        if (mRingPath == null) {
583            mRingPath = new Path();
584        } else {
585            mRingPath.reset();
586        }
587
588        final Path ringPath = mRingPath;
589        // arcTo treats the sweep angle mod 360, so check for that, since we
590        // think 360 means draw the entire oval
591        if (sweep < 360 && sweep > -360) {
592            ringPath.setFillType(Path.FillType.EVEN_ODD);
593            // inner top
594            ringPath.moveTo(x + radius, y);
595            // outer top
596            ringPath.lineTo(x + radius + thickness, y);
597            // outer arc
598            ringPath.arcTo(bounds, 0.0f, sweep, false);
599            // inner arc
600            ringPath.arcTo(innerBounds, sweep, -sweep, false);
601            ringPath.close();
602        } else {
603            // add the entire ovals
604            ringPath.addOval(bounds, Path.Direction.CW);
605            ringPath.addOval(innerBounds, Path.Direction.CCW);
606        }
607
608        return ringPath;
609    }
610
611    /**
612     * <p>Changes this drawbale to use a single color instead of a gradient.</p>
613     * <p><strong>Note</strong>: changing color will affect all instances
614     * of a drawable loaded from a resource. It is recommended to invoke
615     * {@link #mutate()} before changing the color.</p>
616     *
617     * @param argb The color used to fill the shape
618     *
619     * @see #mutate()
620     * @see #setColors(int[])
621     */
622    public void setColor(int argb) {
623        mGradientState.setSolidColor(argb);
624        mFillPaint.setColor(argb);
625        invalidateSelf();
626    }
627
628    @Override
629    public int getChangingConfigurations() {
630        return super.getChangingConfigurations() | mGradientState.mChangingConfigurations;
631    }
632
633    @Override
634    public void setAlpha(int alpha) {
635        if (alpha != mAlpha) {
636            mAlpha = alpha;
637            invalidateSelf();
638        }
639    }
640
641    @Override
642    public void setDither(boolean dither) {
643        if (dither != mDither) {
644            mDither = dither;
645            invalidateSelf();
646        }
647    }
648
649    @Override
650    public void setColorFilter(ColorFilter cf) {
651        if (cf != mColorFilter) {
652            mColorFilter = cf;
653            invalidateSelf();
654        }
655    }
656
657    @Override
658    public int getOpacity() {
659        return mGradientState.mOpaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
660    }
661
662    @Override
663    protected void onBoundsChange(Rect r) {
664        super.onBoundsChange(r);
665        mRingPath = null;
666        mPathIsDirty = true;
667        mRectIsDirty = true;
668    }
669
670    @Override
671    protected boolean onLevelChange(int level) {
672        super.onLevelChange(level);
673        mRectIsDirty = true;
674        mPathIsDirty = true;
675        invalidateSelf();
676        return true;
677    }
678
679    /**
680     * This checks mRectIsDirty, and if it is true, recomputes both our drawing
681     * rectangle (mRect) and the gradient itself, since it depends on our
682     * rectangle too.
683     * @return true if the resulting rectangle is not empty, false otherwise
684     */
685    private boolean ensureValidRect() {
686        if (mRectIsDirty) {
687            mRectIsDirty = false;
688
689            Rect bounds = getBounds();
690            float inset = 0;
691
692            if (mStrokePaint != null) {
693                inset = mStrokePaint.getStrokeWidth() * 0.5f;
694            }
695
696            final GradientState st = mGradientState;
697
698            mRect.set(bounds.left + inset, bounds.top + inset,
699                      bounds.right - inset, bounds.bottom - inset);
700
701            final int[] colors = st.mColors;
702            if (colors != null) {
703                RectF r = mRect;
704                float x0, x1, y0, y1;
705
706                if (st.mGradient == LINEAR_GRADIENT) {
707                    final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f;
708                    switch (st.mOrientation) {
709                    case TOP_BOTTOM:
710                        x0 = r.left;            y0 = r.top;
711                        x1 = x0;                y1 = level * r.bottom;
712                        break;
713                    case TR_BL:
714                        x0 = r.right;           y0 = r.top;
715                        x1 = level * r.left;    y1 = level * r.bottom;
716                        break;
717                    case RIGHT_LEFT:
718                        x0 = r.right;           y0 = r.top;
719                        x1 = level * r.left;    y1 = y0;
720                        break;
721                    case BR_TL:
722                        x0 = r.right;           y0 = r.bottom;
723                        x1 = level * r.left;    y1 = level * r.top;
724                        break;
725                    case BOTTOM_TOP:
726                        x0 = r.left;            y0 = r.bottom;
727                        x1 = x0;                y1 = level * r.top;
728                        break;
729                    case BL_TR:
730                        x0 = r.left;            y0 = r.bottom;
731                        x1 = level * r.right;   y1 = level * r.top;
732                        break;
733                    case LEFT_RIGHT:
734                        x0 = r.left;            y0 = r.top;
735                        x1 = level * r.right;   y1 = y0;
736                        break;
737                    default:/* TL_BR */
738                        x0 = r.left;            y0 = r.top;
739                        x1 = level * r.right;   y1 = level * r.bottom;
740                        break;
741                    }
742
743                    mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
744                            colors, st.mPositions, Shader.TileMode.CLAMP));
745                    if (!mGradientState.mHasSolidColor) {
746                        mFillPaint.setColor(mAlpha << 24);
747                    }
748                } else if (st.mGradient == RADIAL_GRADIENT) {
749                    x0 = r.left + (r.right - r.left) * st.mCenterX;
750                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
751
752                    final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f;
753
754                    mFillPaint.setShader(new RadialGradient(x0, y0,
755                            level * st.mGradientRadius, colors, null,
756                            Shader.TileMode.CLAMP));
757                    if (!mGradientState.mHasSolidColor) {
758                        mFillPaint.setColor(mAlpha << 24);
759                    }
760                } else if (st.mGradient == SWEEP_GRADIENT) {
761                    x0 = r.left + (r.right - r.left) * st.mCenterX;
762                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
763
764                    int[] tempColors = colors;
765                    float[] tempPositions = null;
766
767                    if (st.mUseLevel) {
768                        tempColors = st.mTempColors;
769                        final int length = colors.length;
770                        if (tempColors == null || tempColors.length != length + 1) {
771                            tempColors = st.mTempColors = new int[length + 1];
772                        }
773                        System.arraycopy(colors, 0, tempColors, 0, length);
774                        tempColors[length] = colors[length - 1];
775
776                        tempPositions = st.mTempPositions;
777                        final float fraction = 1.0f / (float) (length - 1);
778                        if (tempPositions == null || tempPositions.length != length + 1) {
779                            tempPositions = st.mTempPositions = new float[length + 1];
780                        }
781
782                        final float level = (float) getLevel() / 10000.0f;
783                        for (int i = 0; i < length; i++) {
784                            tempPositions[i] = i * fraction * level;
785                        }
786                        tempPositions[length] = 1.0f;
787
788                    }
789                    mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
790                    if (!mGradientState.mHasSolidColor) {
791                        mFillPaint.setColor(mAlpha << 24);
792                    }
793                }
794            }
795        }
796        return !mRect.isEmpty();
797    }
798
799    @Override
800    public void inflate(Resources r, XmlPullParser parser,
801            AttributeSet attrs)
802            throws XmlPullParserException, IOException {
803
804        final GradientState st = mGradientState;
805
806        TypedArray a = r.obtainAttributes(attrs,
807                com.android.internal.R.styleable.GradientDrawable);
808
809        super.inflateWithAttributes(r, parser, a,
810                com.android.internal.R.styleable.GradientDrawable_visible);
811
812        int shapeType = a.getInt(
813                com.android.internal.R.styleable.GradientDrawable_shape, RECTANGLE);
814        boolean dither = a.getBoolean(
815                com.android.internal.R.styleable.GradientDrawable_dither, false);
816
817        if (shapeType == RING) {
818            st.mInnerRadius = a.getDimensionPixelSize(
819                    com.android.internal.R.styleable.GradientDrawable_innerRadius, -1);
820            if (st.mInnerRadius == -1) {
821                st.mInnerRadiusRatio = a.getFloat(
822                        com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f);
823            }
824            st.mThickness = a.getDimensionPixelSize(
825                    com.android.internal.R.styleable.GradientDrawable_thickness, -1);
826            if (st.mThickness == -1) {
827                st.mThicknessRatio = a.getFloat(
828                        com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f);
829            }
830            st.mUseLevelForShape = a.getBoolean(
831                    com.android.internal.R.styleable.GradientDrawable_useLevel, true);
832        }
833
834        a.recycle();
835
836        setShape(shapeType);
837        setDither(dither);
838
839        int type;
840
841        final int innerDepth = parser.getDepth() + 1;
842        int depth;
843        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
844               && ((depth=parser.getDepth()) >= innerDepth
845                       || type != XmlPullParser.END_TAG)) {
846            if (type != XmlPullParser.START_TAG) {
847                continue;
848            }
849
850            if (depth > innerDepth) {
851                continue;
852            }
853
854            String name = parser.getName();
855
856            if (name.equals("size")) {
857                a = r.obtainAttributes(attrs,
858                        com.android.internal.R.styleable.GradientDrawableSize);
859                int width = a.getDimensionPixelSize(
860                        com.android.internal.R.styleable.GradientDrawableSize_width, -1);
861                int height = a.getDimensionPixelSize(
862                        com.android.internal.R.styleable.GradientDrawableSize_height, -1);
863                a.recycle();
864                setSize(width, height);
865            } else if (name.equals("gradient")) {
866                a = r.obtainAttributes(attrs,
867                        com.android.internal.R.styleable.GradientDrawableGradient);
868                int startColor = a.getColor(
869                        com.android.internal.R.styleable.GradientDrawableGradient_startColor, 0);
870                boolean hasCenterColor = a
871                        .hasValue(com.android.internal.R.styleable.GradientDrawableGradient_centerColor);
872                int centerColor = a.getColor(
873                        com.android.internal.R.styleable.GradientDrawableGradient_centerColor, 0);
874                int endColor = a.getColor(
875                        com.android.internal.R.styleable.GradientDrawableGradient_endColor, 0);
876                int gradientType = a.getInt(
877                        com.android.internal.R.styleable.GradientDrawableGradient_type,
878                        LINEAR_GRADIENT);
879
880                st.mCenterX = getFloatOrFraction(
881                        a,
882                        com.android.internal.R.styleable.GradientDrawableGradient_centerX,
883                        0.5f);
884
885                st.mCenterY = getFloatOrFraction(
886                        a,
887                        com.android.internal.R.styleable.GradientDrawableGradient_centerY,
888                        0.5f);
889
890                st.mUseLevel = a.getBoolean(
891                        com.android.internal.R.styleable.GradientDrawableGradient_useLevel, false);
892                st.mGradient = gradientType;
893
894                if (gradientType == LINEAR_GRADIENT) {
895                    int angle = (int)a.getFloat(
896                            com.android.internal.R.styleable.GradientDrawableGradient_angle, 0);
897                    angle %= 360;
898                    if (angle % 45 != 0) {
899                        throw new XmlPullParserException(a.getPositionDescription()
900                                + "<gradient> tag requires 'angle' attribute to "
901                                + "be a multiple of 45");
902                    }
903
904                    switch (angle) {
905                    case 0:
906                        st.mOrientation = Orientation.LEFT_RIGHT;
907                        break;
908                    case 45:
909                        st.mOrientation = Orientation.BL_TR;
910                        break;
911                    case 90:
912                        st.mOrientation = Orientation.BOTTOM_TOP;
913                        break;
914                    case 135:
915                        st.mOrientation = Orientation.BR_TL;
916                        break;
917                    case 180:
918                        st.mOrientation = Orientation.RIGHT_LEFT;
919                        break;
920                    case 225:
921                        st.mOrientation = Orientation.TR_BL;
922                        break;
923                    case 270:
924                        st.mOrientation = Orientation.TOP_BOTTOM;
925                        break;
926                    case 315:
927                        st.mOrientation = Orientation.TL_BR;
928                        break;
929                    }
930                } else {
931                    TypedValue tv = a.peekValue(
932                            com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius);
933                    if (tv != null) {
934                        boolean radiusRel = tv.type == TypedValue.TYPE_FRACTION;
935                        st.mGradientRadius = radiusRel ?
936                                tv.getFraction(1.0f, 1.0f) : tv.getFloat();
937                    } else if (gradientType == RADIAL_GRADIENT) {
938                        throw new XmlPullParserException(
939                                a.getPositionDescription()
940                                + "<gradient> tag requires 'gradientRadius' "
941                                + "attribute with radial type");
942                    }
943                }
944
945                a.recycle();
946
947                if (hasCenterColor) {
948                    st.mColors = new int[3];
949                    st.mColors[0] = startColor;
950                    st.mColors[1] = centerColor;
951                    st.mColors[2] = endColor;
952
953                    st.mPositions = new float[3];
954                    st.mPositions[0] = 0.0f;
955                    // Since 0.5f is default value, try to take the one that isn't 0.5f
956                    st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY;
957                    st.mPositions[2] = 1f;
958                } else {
959                    st.mColors = new int[2];
960                    st.mColors[0] = startColor;
961                    st.mColors[1] = endColor;
962                }
963
964            } else if (name.equals("solid")) {
965                a = r.obtainAttributes(attrs,
966                        com.android.internal.R.styleable.GradientDrawableSolid);
967                int argb = a.getColor(
968                        com.android.internal.R.styleable.GradientDrawableSolid_color, 0);
969                a.recycle();
970                setColor(argb);
971            } else if (name.equals("stroke")) {
972                a = r.obtainAttributes(attrs,
973                        com.android.internal.R.styleable.GradientDrawableStroke);
974                int width = a.getDimensionPixelSize(
975                        com.android.internal.R.styleable.GradientDrawableStroke_width, 0);
976                int color = a.getColor(
977                        com.android.internal.R.styleable.GradientDrawableStroke_color, 0);
978                float dashWidth = a.getDimension(
979                        com.android.internal.R.styleable.GradientDrawableStroke_dashWidth, 0);
980                if (dashWidth != 0.0f) {
981                    float dashGap = a.getDimension(
982                            com.android.internal.R.styleable.GradientDrawableStroke_dashGap, 0);
983                    setStroke(width, color, dashWidth, dashGap);
984                } else {
985                    setStroke(width, color);
986                }
987                a.recycle();
988            } else if (name.equals("corners")) {
989                a = r.obtainAttributes(attrs,
990                        com.android.internal.R.styleable.DrawableCorners);
991                int radius = a.getDimensionPixelSize(
992                        com.android.internal.R.styleable.DrawableCorners_radius, 0);
993                setCornerRadius(radius);
994                int topLeftRadius = a.getDimensionPixelSize(
995                        com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius);
996                int topRightRadius = a.getDimensionPixelSize(
997                        com.android.internal.R.styleable.DrawableCorners_topRightRadius, radius);
998                int bottomLeftRadius = a.getDimensionPixelSize(
999                        com.android.internal.R.styleable.DrawableCorners_bottomLeftRadius, radius);
1000                int bottomRightRadius = a.getDimensionPixelSize(
1001                        com.android.internal.R.styleable.DrawableCorners_bottomRightRadius, radius);
1002                if (topLeftRadius != radius || topRightRadius != radius ||
1003                        bottomLeftRadius != radius || bottomRightRadius != radius) {
1004                    // The corner radii are specified in clockwise order (see Path.addRoundRect())
1005                    setCornerRadii(new float[] {
1006                            topLeftRadius, topLeftRadius,
1007                            topRightRadius, topRightRadius,
1008                            bottomRightRadius, bottomRightRadius,
1009                            bottomLeftRadius, bottomLeftRadius
1010                    });
1011                }
1012                a.recycle();
1013            } else if (name.equals("padding")) {
1014                a = r.obtainAttributes(attrs,
1015                        com.android.internal.R.styleable.GradientDrawablePadding);
1016                mPadding = new Rect(
1017                        a.getDimensionPixelOffset(
1018                                com.android.internal.R.styleable.GradientDrawablePadding_left, 0),
1019                        a.getDimensionPixelOffset(
1020                                com.android.internal.R.styleable.GradientDrawablePadding_top, 0),
1021                        a.getDimensionPixelOffset(
1022                                com.android.internal.R.styleable.GradientDrawablePadding_right, 0),
1023                        a.getDimensionPixelOffset(
1024                                com.android.internal.R.styleable.GradientDrawablePadding_bottom, 0));
1025                a.recycle();
1026                mGradientState.mPadding = mPadding;
1027            } else {
1028                Log.w("drawable", "Bad element under <shape>: " + name);
1029            }
1030
1031        }
1032
1033        mGradientState.computeOpacity();
1034    }
1035
1036    private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) {
1037        TypedValue tv = a.peekValue(index);
1038        float v = defaultValue;
1039        if (tv != null) {
1040            boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION;
1041            v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
1042        }
1043        return v;
1044    }
1045
1046    @Override
1047    public int getIntrinsicWidth() {
1048        return mGradientState.mWidth;
1049    }
1050
1051    @Override
1052    public int getIntrinsicHeight() {
1053        return mGradientState.mHeight;
1054    }
1055
1056    @Override
1057    public ConstantState getConstantState() {
1058        mGradientState.mChangingConfigurations = getChangingConfigurations();
1059        return mGradientState;
1060    }
1061
1062    @Override
1063    public Drawable mutate() {
1064        if (!mMutated && super.mutate() == this) {
1065            mGradientState = new GradientState(mGradientState);
1066            initializeWithState(mGradientState);
1067            mMutated = true;
1068        }
1069        return this;
1070    }
1071
1072    final static class GradientState extends ConstantState {
1073        public int mChangingConfigurations;
1074        public int mShape = RECTANGLE;
1075        public int mGradient = LINEAR_GRADIENT;
1076        public Orientation mOrientation;
1077        public int[] mColors;
1078        public int[] mTempColors; // no need to copy
1079        public float[] mTempPositions; // no need to copy
1080        public float[] mPositions;
1081        public boolean mHasSolidColor;
1082        public int mSolidColor;
1083        public int mStrokeWidth = -1;   // if >= 0 use stroking.
1084        public int mStrokeColor;
1085        public float mStrokeDashWidth;
1086        public float mStrokeDashGap;
1087        public float mRadius;    // use this if mRadiusArray is null
1088        public float[] mRadiusArray;
1089        public Rect mPadding;
1090        public int mWidth = -1;
1091        public int mHeight = -1;
1092        public float mInnerRadiusRatio;
1093        public float mThicknessRatio;
1094        public int mInnerRadius;
1095        public int mThickness;
1096        private float mCenterX = 0.5f;
1097        private float mCenterY = 0.5f;
1098        private float mGradientRadius = 0.5f;
1099        private boolean mUseLevel;
1100        private boolean mUseLevelForShape;
1101        private boolean mOpaque;
1102
1103        GradientState(Orientation orientation, int[] colors) {
1104            mOrientation = orientation;
1105            setColors(colors);
1106        }
1107
1108        public GradientState(GradientState state) {
1109            mChangingConfigurations = state.mChangingConfigurations;
1110            mShape = state.mShape;
1111            mGradient = state.mGradient;
1112            mOrientation = state.mOrientation;
1113            if (state.mColors != null) {
1114                mColors = state.mColors.clone();
1115            }
1116            if (state.mPositions != null) {
1117                mPositions = state.mPositions.clone();
1118            }
1119            mHasSolidColor = state.mHasSolidColor;
1120            mSolidColor = state.mSolidColor;
1121            mStrokeWidth = state.mStrokeWidth;
1122            mStrokeColor = state.mStrokeColor;
1123            mStrokeDashWidth = state.mStrokeDashWidth;
1124            mStrokeDashGap = state.mStrokeDashGap;
1125            mRadius = state.mRadius;
1126            if (state.mRadiusArray != null) {
1127                mRadiusArray = state.mRadiusArray.clone();
1128            }
1129            if (state.mPadding != null) {
1130                mPadding = new Rect(state.mPadding);
1131            }
1132            mWidth = state.mWidth;
1133            mHeight = state.mHeight;
1134            mInnerRadiusRatio = state.mInnerRadiusRatio;
1135            mThicknessRatio = state.mThicknessRatio;
1136            mInnerRadius = state.mInnerRadius;
1137            mThickness = state.mThickness;
1138            mCenterX = state.mCenterX;
1139            mCenterY = state.mCenterY;
1140            mGradientRadius = state.mGradientRadius;
1141            mUseLevel = state.mUseLevel;
1142            mUseLevelForShape = state.mUseLevelForShape;
1143            mOpaque = state.mOpaque;
1144        }
1145
1146        @Override
1147        public Drawable newDrawable() {
1148            return new GradientDrawable(this);
1149        }
1150
1151        @Override
1152        public Drawable newDrawable(Resources res) {
1153            return new GradientDrawable(this);
1154        }
1155
1156        @Override
1157        public int getChangingConfigurations() {
1158            return mChangingConfigurations;
1159        }
1160
1161        public void setShape(int shape) {
1162            mShape = shape;
1163            computeOpacity();
1164        }
1165
1166        public void setGradientType(int gradient) {
1167            mGradient = gradient;
1168        }
1169
1170        public void setGradientCenter(float x, float y) {
1171            mCenterX = x;
1172            mCenterY = y;
1173        }
1174
1175        public void setColors(int[] colors) {
1176            mHasSolidColor = false;
1177            mColors = colors;
1178            computeOpacity();
1179        }
1180
1181        public void setSolidColor(int argb) {
1182            mHasSolidColor = true;
1183            mSolidColor = argb;
1184            mColors = null;
1185            computeOpacity();
1186        }
1187
1188        private void computeOpacity() {
1189            if (mShape != RECTANGLE) {
1190                mOpaque = false;
1191                return;
1192            }
1193
1194            if (mRadius > 0 || mRadiusArray != null) {
1195                mOpaque = false;
1196                return;
1197            }
1198
1199            if (mStrokeWidth > 0 && !isOpaque(mStrokeColor)) {
1200                mOpaque = false;
1201                return;
1202            }
1203
1204            if (mHasSolidColor) {
1205                mOpaque = isOpaque(mSolidColor);
1206                return;
1207            }
1208
1209            if (mColors != null) {
1210                for (int i = 0; i < mColors.length; i++) {
1211                    if (!isOpaque(mColors[i])) {
1212                        mOpaque = false;
1213                        return;
1214                    }
1215                }
1216            }
1217
1218            mOpaque = true;
1219        }
1220
1221        private static boolean isOpaque(int color) {
1222            return ((color >> 24) & 0xff) == 0xff;
1223        }
1224
1225        public void setStroke(int width, int color) {
1226            mStrokeWidth = width;
1227            mStrokeColor = color;
1228            computeOpacity();
1229        }
1230
1231        public void setStroke(int width, int color, float dashWidth, float dashGap) {
1232            mStrokeWidth = width;
1233            mStrokeColor = color;
1234            mStrokeDashWidth = dashWidth;
1235            mStrokeDashGap = dashGap;
1236            computeOpacity();
1237        }
1238
1239        public void setCornerRadius(float radius) {
1240            if (radius < 0) {
1241                radius = 0;
1242            }
1243            mRadius = radius;
1244            mRadiusArray = null;
1245        }
1246
1247        public void setCornerRadii(float[] radii) {
1248            mRadiusArray = radii;
1249            if (radii == null) {
1250                mRadius = 0;
1251            }
1252        }
1253
1254        public void setSize(int width, int height) {
1255            mWidth = width;
1256            mHeight = height;
1257        }
1258
1259        public void setGradientRadius(float gradientRadius) {
1260            mGradientRadius = gradientRadius;
1261        }
1262    }
1263
1264    private GradientDrawable(GradientState state) {
1265        mGradientState = state;
1266        initializeWithState(state);
1267        mRectIsDirty = true;
1268        mMutated = false;
1269    }
1270
1271    private void initializeWithState(GradientState state) {
1272        if (state.mHasSolidColor) {
1273            mFillPaint.setColor(state.mSolidColor);
1274        } else if (state.mColors == null) {
1275            // If we don't have a solid color and we don't have a gradient,
1276            // the app is stroking the shape, set the color to the default
1277            // value of state.mSolidColor
1278            mFillPaint.setColor(0);
1279        }
1280        mPadding = state.mPadding;
1281        if (state.mStrokeWidth >= 0) {
1282            mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1283            mStrokePaint.setStyle(Paint.Style.STROKE);
1284            mStrokePaint.setStrokeWidth(state.mStrokeWidth);
1285            mStrokePaint.setColor(state.mStrokeColor);
1286
1287            if (state.mStrokeDashWidth != 0.0f) {
1288                DashPathEffect e = new DashPathEffect(
1289                        new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
1290                mStrokePaint.setPathEffect(e);
1291            }
1292        }
1293    }
1294}
1295