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