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