GradientDrawable.java revision e6a39b12656ab8d5c77d8366b24aa6410fd42e11
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                canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
616                break;
617            }
618            case RING:
619                Path path = buildRing(st);
620                canvas.drawPath(path, mFillPaint);
621                if (haveStroke) {
622                    canvas.drawPath(path, mStrokePaint);
623                }
624                break;
625        }
626
627        if (useLayer) {
628            canvas.restore();
629        } else {
630            mFillPaint.setAlpha(prevFillAlpha);
631            if (haveStroke) {
632                mStrokePaint.setAlpha(prevStrokeAlpha);
633            }
634        }
635    }
636
637    private void buildPathIfDirty() {
638        final GradientState st = mGradientState;
639        if (mPathIsDirty || mRectIsDirty) {
640            mPath.reset();
641            mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW);
642            mPathIsDirty = mRectIsDirty = false;
643        }
644    }
645
646    private Path buildRing(GradientState st) {
647        if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
648        mPathIsDirty = false;
649
650        float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f;
651
652        RectF bounds = new RectF(mRect);
653
654        float x = bounds.width() / 2.0f;
655        float y = bounds.height() / 2.0f;
656
657        float thickness = st.mThickness != -1 ?
658                st.mThickness : bounds.width() / st.mThicknessRatio;
659        // inner radius
660        float radius = st.mInnerRadius != -1 ?
661                st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio;
662
663        RectF innerBounds = new RectF(bounds);
664        innerBounds.inset(x - radius, y - radius);
665
666        bounds = new RectF(innerBounds);
667        bounds.inset(-thickness, -thickness);
668
669        if (mRingPath == null) {
670            mRingPath = new Path();
671        } else {
672            mRingPath.reset();
673        }
674
675        final Path ringPath = mRingPath;
676        // arcTo treats the sweep angle mod 360, so check for that, since we
677        // think 360 means draw the entire oval
678        if (sweep < 360 && sweep > -360) {
679            ringPath.setFillType(Path.FillType.EVEN_ODD);
680            // inner top
681            ringPath.moveTo(x + radius, y);
682            // outer top
683            ringPath.lineTo(x + radius + thickness, y);
684            // outer arc
685            ringPath.arcTo(bounds, 0.0f, sweep, false);
686            // inner arc
687            ringPath.arcTo(innerBounds, sweep, -sweep, false);
688            ringPath.close();
689        } else {
690            // add the entire ovals
691            ringPath.addOval(bounds, Path.Direction.CW);
692            ringPath.addOval(innerBounds, Path.Direction.CCW);
693        }
694
695        return ringPath;
696    }
697
698    /**
699     * <p>Changes this drawable to use a single color instead of a gradient.</p>
700     * <p><strong>Note</strong>: changing color will affect all instances
701     * of a drawable loaded from a resource. It is recommended to invoke
702     * {@link #mutate()} before changing the color.</p>
703     *
704     * @param argb The color used to fill the shape
705     *
706     * @see #mutate()
707     * @see #setColors(int[])
708     */
709    public void setColor(int argb) {
710        mGradientState.setColorStateList(ColorStateList.valueOf(argb));
711        mFillPaint.setColor(argb);
712        invalidateSelf();
713    }
714
715    /**
716     * Changes this drawable to use a single color state list instead of a
717     * gradient. Calling this method with a null argument will clear the color
718     * and is equivalent to calling {@link #setColor(int)} with the argument
719     * {@link Color#TRANSPARENT}.
720     * <p>
721     * <strong>Note</strong>: changing color will affect all instances of a
722     * drawable loaded from a resource. It is recommended to invoke
723     * {@link #mutate()} before changing the color.</p>
724     *
725     * @param colorStateList The color state list used to fill the shape
726     * @see #mutate()
727     */
728    public void setColor(ColorStateList colorStateList) {
729        mGradientState.setColorStateList(colorStateList);
730        final int color;
731        if (colorStateList == null) {
732            color = Color.TRANSPARENT;
733        } else {
734            final int[] stateSet = getState();
735            color = colorStateList.getColorForState(stateSet, 0);
736        }
737        mFillPaint.setColor(color);
738        invalidateSelf();
739    }
740
741    @Override
742    protected boolean onStateChange(int[] stateSet) {
743        boolean invalidateSelf = false;
744
745        final GradientState s = mGradientState;
746        final ColorStateList stateList = s.mColorStateList;
747        if (stateList != null) {
748            final int newColor = stateList.getColorForState(stateSet, 0);
749            final int oldColor = mFillPaint.getColor();
750            if (oldColor != newColor) {
751                mFillPaint.setColor(newColor);
752                invalidateSelf = true;
753            }
754        }
755
756        final Paint strokePaint = mStrokePaint;
757        if (strokePaint != null) {
758            final ColorStateList strokeStateList = s.mStrokeColorStateList;
759            if (strokeStateList != null) {
760                final int newStrokeColor = strokeStateList.getColorForState(stateSet, 0);
761                final int oldStrokeColor = strokePaint.getColor();
762                if (oldStrokeColor != newStrokeColor) {
763                    strokePaint.setColor(newStrokeColor);
764                    invalidateSelf = true;
765                }
766            }
767        }
768
769        if (invalidateSelf) {
770            invalidateSelf();
771            return true;
772        }
773
774        return false;
775    }
776
777    @Override
778    public boolean isStateful() {
779        final GradientState s = mGradientState;
780        return super.isStateful()
781                || (s.mColorStateList != null && s.mColorStateList.isStateful())
782                || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful());
783    }
784
785    @Override
786    public int getChangingConfigurations() {
787        return super.getChangingConfigurations() | mGradientState.mChangingConfigurations;
788    }
789
790    @Override
791    public void setAlpha(int alpha) {
792        if (alpha != mAlpha) {
793            mAlpha = alpha;
794            invalidateSelf();
795        }
796    }
797
798    @Override
799    public int getAlpha() {
800        return mAlpha;
801    }
802
803    @Override
804    public void setDither(boolean dither) {
805        if (dither != mDither) {
806            mDither = dither;
807            invalidateSelf();
808        }
809    }
810
811    @Override
812    public void setColorFilter(ColorFilter cf) {
813        if (cf != mColorFilter) {
814            mColorFilter = cf;
815            invalidateSelf();
816        }
817    }
818
819    @Override
820    public int getOpacity() {
821        return mGradientState.mOpaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
822    }
823
824    @Override
825    protected void onBoundsChange(Rect r) {
826        super.onBoundsChange(r);
827        mRingPath = null;
828        mPathIsDirty = true;
829        mRectIsDirty = true;
830    }
831
832    @Override
833    protected boolean onLevelChange(int level) {
834        super.onLevelChange(level);
835        mRectIsDirty = true;
836        mPathIsDirty = true;
837        invalidateSelf();
838        return true;
839    }
840
841    /**
842     * This checks mRectIsDirty, and if it is true, recomputes both our drawing
843     * rectangle (mRect) and the gradient itself, since it depends on our
844     * rectangle too.
845     * @return true if the resulting rectangle is not empty, false otherwise
846     */
847    private boolean ensureValidRect() {
848        if (mRectIsDirty) {
849            mRectIsDirty = false;
850
851            Rect bounds = getBounds();
852            float inset = 0;
853
854            if (mStrokePaint != null) {
855                inset = mStrokePaint.getStrokeWidth() * 0.5f;
856            }
857
858            final GradientState st = mGradientState;
859
860            mRect.set(bounds.left + inset, bounds.top + inset,
861                      bounds.right - inset, bounds.bottom - inset);
862
863            final int[] colors = st.mColors;
864            if (colors != null) {
865                RectF r = mRect;
866                float x0, x1, y0, y1;
867
868                if (st.mGradient == LINEAR_GRADIENT) {
869                    final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
870                    switch (st.mOrientation) {
871                    case TOP_BOTTOM:
872                        x0 = r.left;            y0 = r.top;
873                        x1 = x0;                y1 = level * r.bottom;
874                        break;
875                    case TR_BL:
876                        x0 = r.right;           y0 = r.top;
877                        x1 = level * r.left;    y1 = level * r.bottom;
878                        break;
879                    case RIGHT_LEFT:
880                        x0 = r.right;           y0 = r.top;
881                        x1 = level * r.left;    y1 = y0;
882                        break;
883                    case BR_TL:
884                        x0 = r.right;           y0 = r.bottom;
885                        x1 = level * r.left;    y1 = level * r.top;
886                        break;
887                    case BOTTOM_TOP:
888                        x0 = r.left;            y0 = r.bottom;
889                        x1 = x0;                y1 = level * r.top;
890                        break;
891                    case BL_TR:
892                        x0 = r.left;            y0 = r.bottom;
893                        x1 = level * r.right;   y1 = level * r.top;
894                        break;
895                    case LEFT_RIGHT:
896                        x0 = r.left;            y0 = r.top;
897                        x1 = level * r.right;   y1 = y0;
898                        break;
899                    default:/* TL_BR */
900                        x0 = r.left;            y0 = r.top;
901                        x1 = level * r.right;   y1 = level * r.bottom;
902                        break;
903                    }
904
905                    mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
906                            colors, st.mPositions, Shader.TileMode.CLAMP));
907                } else if (st.mGradient == RADIAL_GRADIENT) {
908                    x0 = r.left + (r.right - r.left) * st.mCenterX;
909                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
910
911                    float radius = st.mGradientRadius;
912                    if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
913                        radius *= Math.min(st.mWidth, st.mHeight);
914                    } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
915                        radius *= Math.min(r.width(), r.height());
916                    }
917
918                    if (st.mUseLevel) {
919                        radius *= getLevel() / 10000.0f;
920                    }
921
922                    mGradientRadius = radius;
923
924                    if (radius == 0) {
925                        // We can't have a shader with zero radius, so let's
926                        // have a very, very small radius.
927                        radius = 0.001f;
928                    }
929
930                    mFillPaint.setShader(new RadialGradient(
931                            x0, y0, radius, colors, null, Shader.TileMode.CLAMP));
932                } else if (st.mGradient == SWEEP_GRADIENT) {
933                    x0 = r.left + (r.right - r.left) * st.mCenterX;
934                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
935
936                    int[] tempColors = colors;
937                    float[] tempPositions = null;
938
939                    if (st.mUseLevel) {
940                        tempColors = st.mTempColors;
941                        final int length = colors.length;
942                        if (tempColors == null || tempColors.length != length + 1) {
943                            tempColors = st.mTempColors = new int[length + 1];
944                        }
945                        System.arraycopy(colors, 0, tempColors, 0, length);
946                        tempColors[length] = colors[length - 1];
947
948                        tempPositions = st.mTempPositions;
949                        final float fraction = 1.0f / (length - 1);
950                        if (tempPositions == null || tempPositions.length != length + 1) {
951                            tempPositions = st.mTempPositions = new float[length + 1];
952                        }
953
954                        final float level = getLevel() / 10000.0f;
955                        for (int i = 0; i < length; i++) {
956                            tempPositions[i] = i * fraction * level;
957                        }
958                        tempPositions[length] = 1.0f;
959
960                    }
961                    mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
962                }
963
964                // If we don't have a solid color, the alpha channel must be
965                // maxed out so that alpha modulation works correctly.
966                if (st.mColorStateList == null) {
967                    mFillPaint.setColor(Color.BLACK);
968                }
969            }
970        }
971        return !mRect.isEmpty();
972    }
973
974    @Override
975    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
976            throws XmlPullParserException, IOException {
977        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
978        super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible);
979
980        inflateStateFromTypedArray(a);
981        a.recycle();
982
983        inflateChildElements(r, parser, attrs, theme);
984
985        mGradientState.computeOpacity();
986    }
987
988    /**
989     * Initializes the constant state from the values in the typed array.
990     */
991    private void inflateStateFromTypedArray(TypedArray a) {
992        final GradientState state = mGradientState;
993
994        // Extract the theme attributes, if any.
995        final int[] themeAttrs = a.extractThemeAttrs();
996        state.mThemeAttrs = themeAttrs;
997
998        final boolean needsRingAttrs;
999        if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_shape] == 0) {
1000            final int shapeType = a.getInt(R.styleable.GradientDrawable_shape, RECTANGLE);
1001            setShape(shapeType);
1002            needsRingAttrs = shapeType == RING;
1003        } else {
1004            needsRingAttrs = true;
1005        }
1006
1007        // We only need to load ring attributes if the shape type is a theme
1008        // attribute (e.g. unknown) or defined in XML as RING.
1009        if (needsRingAttrs) {
1010            if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_innerRadius] == 0) {
1011                state.mInnerRadius = a.getDimensionPixelSize(
1012                        R.styleable.GradientDrawable_innerRadius, -1);
1013            }
1014
1015            if (state.mInnerRadius == -1
1016                    && (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thicknessRatio] == 0)) {
1017                state.mInnerRadiusRatio = a.getFloat(
1018                        R.styleable.GradientDrawable_innerRadiusRatio, DEFAULT_INNER_RADIUS_RATIO);
1019            }
1020
1021            if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thickness] == 0) {
1022                state.mThickness = a.getDimensionPixelSize(
1023                        R.styleable.GradientDrawable_thickness, -1);
1024            }
1025
1026            if (state.mThickness == -1
1027                    && (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thicknessRatio] == 0)) {
1028                state.mThicknessRatio = a.getFloat(
1029                        R.styleable.GradientDrawable_thicknessRatio, DEFAULT_THICKNESS_RATIO);
1030            }
1031
1032            if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_useLevel] == 0) {
1033                state.mUseLevelForShape = a.getBoolean(
1034                        R.styleable.GradientDrawable_useLevel, true);
1035            }
1036        }
1037
1038        if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_dither] == 0) {
1039            final boolean dither = a.getBoolean(R.styleable.GradientDrawable_dither, false);
1040            setDither(dither);
1041        }
1042    }
1043
1044    @Override
1045    public void applyTheme(Theme t) {
1046        super.applyTheme(t);
1047
1048        final GradientState state = mGradientState;
1049        if (state == null) {
1050            throw new RuntimeException("Can't apply theme to <shape> with no constant state");
1051        }
1052
1053        final int[] themeAttrs = state.mThemeAttrs;
1054        if (themeAttrs != null) {
1055            final TypedArray a = t.resolveAttributes(
1056                    themeAttrs, R.styleable.GradientDrawable, 0, 0);
1057            updateStateFromTypedArray(a);
1058            a.recycle();
1059
1060            applyThemeChildElements(t);
1061
1062            mGradientState.computeOpacity();
1063        }
1064    }
1065
1066    /**
1067     * Updates the constant state from the values in the typed array.
1068     */
1069    private void updateStateFromTypedArray(TypedArray a) {
1070        final GradientState state = mGradientState;
1071
1072        if (a.hasValue(R.styleable.GradientDrawable_shape)) {
1073            final int shapeType = a.getInt(R.styleable.GradientDrawable_shape, RECTANGLE);
1074            setShape(shapeType);
1075        }
1076
1077        if (a.hasValue(R.styleable.GradientDrawable_dither)) {
1078            final boolean dither = a.getBoolean(R.styleable.GradientDrawable_dither, false);
1079            setDither(dither);
1080        }
1081
1082        if (state.mShape == RING) {
1083            if (a.hasValue(R.styleable.GradientDrawable_innerRadius)) {
1084                state.mInnerRadius = a.getDimensionPixelSize(
1085                        R.styleable.GradientDrawable_innerRadius, -1);
1086            }
1087
1088            if (state.mInnerRadius == -1 && a.hasValue(
1089                    R.styleable.GradientDrawable_innerRadiusRatio)) {
1090                state.mInnerRadiusRatio = a.getFloat(
1091                        R.styleable.GradientDrawable_innerRadiusRatio, DEFAULT_INNER_RADIUS_RATIO);
1092            }
1093
1094            if (a.hasValue(R.styleable.GradientDrawable_thickness)) {
1095                state.mThickness = a.getDimensionPixelSize(
1096                        R.styleable.GradientDrawable_thickness, -1);
1097            }
1098
1099            if (state.mThickness == -1 && a.hasValue(
1100                    R.styleable.GradientDrawable_thicknessRatio)) {
1101                state.mThicknessRatio = a.getFloat(
1102                        R.styleable.GradientDrawable_thicknessRatio, DEFAULT_THICKNESS_RATIO);
1103            }
1104
1105            if (a.hasValue(R.styleable.GradientDrawable_useLevel)) {
1106                state.mUseLevelForShape = a.getBoolean(
1107                        R.styleable.GradientDrawable_useLevel, true);
1108            }
1109        }
1110    }
1111
1112    @Override
1113    public boolean canApplyTheme() {
1114        final GradientState state = mGradientState;
1115        return state != null && (state.mThemeAttrs != null || state.mAttrSize != null
1116                || state.mAttrGradient != null || state.mAttrSolid != null
1117                || state.mAttrStroke != null || state.mAttrCorners != null
1118                || state.mAttrPadding != null);
1119    }
1120
1121    private void applyThemeChildElements(Theme t) {
1122        final GradientState state = mGradientState;
1123        TypedArray a;
1124
1125        if (state.mAttrSize != null) {
1126            a = t.resolveAttributes(state.mAttrSize, R.styleable.GradientDrawableSize, 0, 0);
1127            // TODO: updateGradientDrawableSize(a);
1128            a.recycle();
1129        }
1130
1131        if (state.mAttrGradient != null) {
1132            a = t.resolveAttributes(state.mAttrGradient, R.styleable.GradientDrawableGradient, 0, 0);
1133            // TODO: updateGradientDrawableGradient(a);
1134            a.recycle();
1135        }
1136
1137        if (state.mAttrSolid != null) {
1138            a = t.resolveAttributes(state.mAttrSolid, R.styleable.GradientDrawableSolid, 0, 0);
1139            // TODO: updateGradientDrawableSolid(a);
1140            a.recycle();
1141        }
1142
1143        if (state.mAttrStroke != null) {
1144            a = t.resolveAttributes(state.mAttrStroke, R.styleable.GradientDrawableStroke, 0, 0);
1145            // TODO: updateGradientDrawableStroke(a);
1146            a.recycle();
1147        }
1148
1149        if (state.mAttrCorners != null) {
1150            a = t.resolveAttributes(state.mAttrCorners, R.styleable.DrawableCorners, 0, 0);
1151            // TODO: updateDrawableCorners(a);
1152            a.recycle();
1153        }
1154
1155        if (state.mAttrPadding != null) {
1156            a = t.resolveAttributes(state.mAttrPadding, R.styleable.GradientDrawablePadding, 0, 0);
1157            // TODO: updateGradientDrawablePadding(a);
1158            a.recycle();
1159        }
1160    }
1161
1162    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
1163            Theme theme) throws XmlPullParserException, IOException {
1164        TypedArray a;
1165        int type;
1166
1167        final int innerDepth = parser.getDepth() + 1;
1168        int depth;
1169        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1170               && ((depth=parser.getDepth()) >= innerDepth
1171                       || type != XmlPullParser.END_TAG)) {
1172            if (type != XmlPullParser.START_TAG) {
1173                continue;
1174            }
1175
1176            if (depth > innerDepth) {
1177                continue;
1178            }
1179
1180            String name = parser.getName();
1181
1182            if (name.equals("size")) {
1183                a = obtainAttributes(
1184                        r, theme, attrs, R.styleable.GradientDrawableSize);
1185                applyGradientDrawableSize(a);
1186                a.recycle();
1187            } else if (name.equals("gradient")) {
1188                a = obtainAttributes(
1189                        r, theme, attrs, R.styleable.GradientDrawableGradient);
1190                applyGradientDrawableGradient(r, a);
1191                a.recycle();
1192            } else if (name.equals("solid")) {
1193                a = obtainAttributes(
1194                        r, theme, attrs, R.styleable.GradientDrawableSolid);
1195                applyGradientDrawableSolid(a);
1196                a.recycle();
1197            } else if (name.equals("stroke")) {
1198                a = obtainAttributes(
1199                        r, theme, attrs, R.styleable.GradientDrawableStroke);
1200                applyGradientDrawableStroke(a);
1201                a.recycle();
1202            } else if (name.equals("corners")) {
1203                a = obtainAttributes(r
1204                        , theme, attrs, R.styleable.DrawableCorners);
1205                applyDrawableCorners(a);
1206                a.recycle();
1207            } else if (name.equals("padding")) {
1208                a = obtainAttributes(
1209                        r, theme, attrs, R.styleable.GradientDrawablePadding);
1210                applyGradientDrawablePadding(a);
1211                a.recycle();
1212            } else {
1213                Log.w("drawable", "Bad element under <shape>: " + name);
1214            }
1215        }
1216    }
1217
1218    private void applyGradientDrawablePadding(TypedArray a) {
1219        mPadding = new Rect(
1220                a.getDimensionPixelOffset(
1221                        R.styleable.GradientDrawablePadding_left, 0),
1222                a.getDimensionPixelOffset(
1223                        R.styleable.GradientDrawablePadding_top, 0),
1224                a.getDimensionPixelOffset(
1225                        R.styleable.GradientDrawablePadding_right, 0),
1226                a.getDimensionPixelOffset(
1227                        R.styleable.GradientDrawablePadding_bottom, 0));
1228        mGradientState.mPadding = mPadding;
1229
1230        // Extract the theme attributes, if any.
1231        mGradientState.mAttrPadding = a.extractThemeAttrs();
1232    }
1233
1234    private void applyDrawableCorners(TypedArray a) {
1235        int radius = a.getDimensionPixelSize(
1236                R.styleable.DrawableCorners_radius, 0);
1237        setCornerRadius(radius);
1238        int topLeftRadius = a.getDimensionPixelSize(
1239                R.styleable.DrawableCorners_topLeftRadius, radius);
1240        int topRightRadius = a.getDimensionPixelSize(
1241                R.styleable.DrawableCorners_topRightRadius, radius);
1242        int bottomLeftRadius = a.getDimensionPixelSize(
1243                R.styleable.DrawableCorners_bottomLeftRadius, radius);
1244        int bottomRightRadius = a.getDimensionPixelSize(
1245                R.styleable.DrawableCorners_bottomRightRadius, radius);
1246        if (topLeftRadius != radius || topRightRadius != radius ||
1247                bottomLeftRadius != radius || bottomRightRadius != radius) {
1248            // The corner radii are specified in clockwise order (see Path.addRoundRect())
1249            setCornerRadii(new float[] {
1250                    topLeftRadius, topLeftRadius,
1251                    topRightRadius, topRightRadius,
1252                    bottomRightRadius, bottomRightRadius,
1253                    bottomLeftRadius, bottomLeftRadius
1254            });
1255        }
1256
1257        // Extract the theme attributes, if any.
1258        mGradientState.mAttrCorners = a.extractThemeAttrs();
1259    }
1260
1261    private void applyGradientDrawableStroke(TypedArray a) {
1262        final int width = a.getDimensionPixelSize(
1263                R.styleable.GradientDrawableStroke_width, 0);
1264        final ColorStateList colorStateList = a.getColorStateList(
1265                R.styleable.GradientDrawableStroke_color);
1266        final float dashWidth = a.getDimension(
1267                R.styleable.GradientDrawableStroke_dashWidth, 0);
1268        if (dashWidth != 0.0f) {
1269            final float dashGap = a.getDimension(
1270                    R.styleable.GradientDrawableStroke_dashGap, 0);
1271            setStroke(width, colorStateList, dashWidth, dashGap);
1272        } else {
1273            setStroke(width, colorStateList);
1274        }
1275
1276        // Extract the theme attributes, if any.
1277        mGradientState.mAttrStroke = a.extractThemeAttrs();
1278    }
1279
1280    private void applyGradientDrawableSolid(TypedArray a) {
1281        final ColorStateList colorStateList = a.getColorStateList(
1282                R.styleable.GradientDrawableSolid_color);
1283        setColor(colorStateList);
1284
1285        // Extract the theme attributes, if any.
1286        mGradientState.mAttrSolid = a.extractThemeAttrs();
1287    }
1288
1289    private void applyGradientDrawableGradient(Resources r, TypedArray a)
1290            throws XmlPullParserException {
1291        final GradientState st = mGradientState;
1292        final int startColor = a.getColor(
1293                R.styleable.GradientDrawableGradient_startColor, 0);
1294        final boolean hasCenterColor = a.hasValue(
1295                R.styleable.GradientDrawableGradient_centerColor);
1296        final int centerColor = a.getColor(
1297                R.styleable.GradientDrawableGradient_centerColor, 0);
1298        final int endColor = a.getColor(
1299                R.styleable.GradientDrawableGradient_endColor, 0);
1300
1301        if (hasCenterColor) {
1302            st.mColors = new int[3];
1303            st.mColors[0] = startColor;
1304            st.mColors[1] = centerColor;
1305            st.mColors[2] = endColor;
1306
1307            st.mPositions = new float[3];
1308            st.mPositions[0] = 0.0f;
1309            // Since 0.5f is default value, try to take the one that isn't 0.5f
1310            st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY;
1311            st.mPositions[2] = 1f;
1312        } else {
1313            st.mColors = new int[2];
1314            st.mColors[0] = startColor;
1315            st.mColors[1] = endColor;
1316        }
1317
1318        st.mCenterX = getFloatOrFraction(
1319                a, R.styleable.GradientDrawableGradient_centerX, 0.5f);
1320        st.mCenterY = getFloatOrFraction(
1321                a, R.styleable.GradientDrawableGradient_centerY, 0.5f);
1322        st.mUseLevel = a.getBoolean(
1323                R.styleable.GradientDrawableGradient_useLevel, false);
1324        st.mGradient = a.getInt(
1325                R.styleable.GradientDrawableGradient_type, LINEAR_GRADIENT);
1326
1327        if (st.mGradient == LINEAR_GRADIENT) {
1328            int angle = (int) a.getFloat(
1329                    R.styleable.GradientDrawableGradient_angle, 0);
1330            angle %= 360;
1331
1332            if (angle % 45 != 0) {
1333                throw new XmlPullParserException(a.getPositionDescription()
1334                        + "<gradient> tag requires 'angle' attribute to "
1335                        + "be a multiple of 45");
1336            }
1337
1338            switch (angle) {
1339                case 0:
1340                    st.mOrientation = Orientation.LEFT_RIGHT;
1341                    break;
1342                case 45:
1343                    st.mOrientation = Orientation.BL_TR;
1344                    break;
1345                case 90:
1346                    st.mOrientation = Orientation.BOTTOM_TOP;
1347                    break;
1348                case 135:
1349                    st.mOrientation = Orientation.BR_TL;
1350                    break;
1351                case 180:
1352                    st.mOrientation = Orientation.RIGHT_LEFT;
1353                    break;
1354                case 225:
1355                    st.mOrientation = Orientation.TR_BL;
1356                    break;
1357                case 270:
1358                    st.mOrientation = Orientation.TOP_BOTTOM;
1359                    break;
1360                case 315:
1361                    st.mOrientation = Orientation.TL_BR;
1362                    break;
1363            }
1364        } else {
1365            final TypedValue tv = a.peekValue(
1366                    R.styleable.GradientDrawableGradient_gradientRadius);
1367            if (tv != null) {
1368                final float radius;
1369                final int radiusType;
1370                if (tv.type == TypedValue.TYPE_FRACTION) {
1371                    radius = tv.getFraction(1.0f, 1.0f);
1372
1373                    final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
1374                            & TypedValue.COMPLEX_UNIT_MASK;
1375                    if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
1376                        radiusType = RADIUS_TYPE_FRACTION_PARENT;
1377                    } else {
1378                        radiusType = RADIUS_TYPE_FRACTION;
1379                    }
1380                } else {
1381                    radius = tv.getDimension(r.getDisplayMetrics());
1382                    radiusType = RADIUS_TYPE_PIXELS;
1383                }
1384
1385                st.mGradientRadius = radius;
1386                st.mGradientRadiusType = radiusType;
1387            } else if (st.mGradient == RADIAL_GRADIENT) {
1388                throw new XmlPullParserException(
1389                        a.getPositionDescription()
1390                        + "<gradient> tag requires 'gradientRadius' "
1391                        + "attribute with radial type");
1392            }
1393        }
1394
1395        // Extract the theme attributes, if any.
1396        mGradientState.mAttrGradient = a.extractThemeAttrs();
1397    }
1398
1399    private void applyGradientDrawableSize(TypedArray a) {
1400        int width = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, -1);
1401        int height = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, -1);
1402        setSize(width, height);
1403
1404        // Extract the theme attributes, if any.
1405        mGradientState.mAttrSize = a.extractThemeAttrs();
1406    }
1407
1408    private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) {
1409        TypedValue tv = a.peekValue(index);
1410        float v = defaultValue;
1411        if (tv != null) {
1412            boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION;
1413            v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
1414        }
1415        return v;
1416    }
1417
1418    @Override
1419    public int getIntrinsicWidth() {
1420        return mGradientState.mWidth;
1421    }
1422
1423    @Override
1424    public int getIntrinsicHeight() {
1425        return mGradientState.mHeight;
1426    }
1427
1428    @Override
1429    public ConstantState getConstantState() {
1430        mGradientState.mChangingConfigurations = getChangingConfigurations();
1431        return mGradientState;
1432    }
1433
1434    @Override
1435    public boolean getOutline(Outline outline) {
1436        final GradientState st = mGradientState;
1437        final Rect bounds = getBounds();
1438
1439        switch (st.mShape) {
1440            case RECTANGLE:
1441                if (st.mRadiusArray != null) {
1442                    buildPathIfDirty();
1443                    outline.setConvexPath(mPath);
1444                    return true;
1445                }
1446
1447                float rad = 0;
1448                if (st.mRadius > 0.0f) {
1449                    // clamp the radius based on width & height, matching behavior in draw()
1450                    rad = Math.min(st.mRadius,
1451                            Math.min(bounds.width(), bounds.height()) * 0.5f);
1452                }
1453                outline.setRoundRect(bounds.left, bounds.top,
1454                        bounds.right, bounds.bottom, rad);
1455                return true;
1456            case LINE: {
1457                float halfStrokeWidth = mStrokePaint.getStrokeWidth() * 0.5f;
1458                float centerY = bounds.centerY();
1459                int top = (int) Math.floor(centerY - halfStrokeWidth);
1460                int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
1461
1462                outline.setRect(bounds.left, top, bounds.right, bottom);
1463                return true;
1464            }
1465            default:
1466                // TODO: investigate
1467                return false;
1468        }
1469    }
1470
1471    @Override
1472    public Drawable mutate() {
1473        if (!mMutated && super.mutate() == this) {
1474            mGradientState = new GradientState(mGradientState);
1475            initializeWithState(mGradientState);
1476            mMutated = true;
1477        }
1478        return this;
1479    }
1480
1481    final static class GradientState extends ConstantState {
1482        public int mChangingConfigurations;
1483        public int mShape = RECTANGLE;
1484        public int mGradient = LINEAR_GRADIENT;
1485        public Orientation mOrientation;
1486        public ColorStateList mColorStateList;
1487        public ColorStateList mStrokeColorStateList;
1488        public int[] mColors;
1489        public int[] mTempColors; // no need to copy
1490        public float[] mTempPositions; // no need to copy
1491        public float[] mPositions;
1492        public int mStrokeWidth = -1;   // if >= 0 use stroking.
1493        public float mStrokeDashWidth;
1494        public float mStrokeDashGap;
1495        public float mRadius;    // use this if mRadiusArray is null
1496        public float[] mRadiusArray;
1497        public Rect mPadding;
1498        public int mWidth = -1;
1499        public int mHeight = -1;
1500        public float mInnerRadiusRatio;
1501        public float mThicknessRatio;
1502        public int mInnerRadius;
1503        public int mThickness;
1504        private float mCenterX = 0.5f;
1505        private float mCenterY = 0.5f;
1506        private float mGradientRadius = 0.5f;
1507        private int mGradientRadiusType = RADIUS_TYPE_PIXELS;
1508        private boolean mUseLevel;
1509        private boolean mUseLevelForShape;
1510        private boolean mOpaque;
1511
1512        int[] mThemeAttrs;
1513        int[] mAttrSize;
1514        int[] mAttrGradient;
1515        int[] mAttrSolid;
1516        int[] mAttrStroke;
1517        int[] mAttrCorners;
1518        int[] mAttrPadding;
1519
1520        GradientState(Orientation orientation, int[] colors) {
1521            mOrientation = orientation;
1522            setColors(colors);
1523        }
1524
1525        public GradientState(GradientState state) {
1526            mChangingConfigurations = state.mChangingConfigurations;
1527            mShape = state.mShape;
1528            mGradient = state.mGradient;
1529            mOrientation = state.mOrientation;
1530            mColorStateList = state.mColorStateList;
1531            if (state.mColors != null) {
1532                mColors = state.mColors.clone();
1533            }
1534            if (state.mPositions != null) {
1535                mPositions = state.mPositions.clone();
1536            }
1537            mStrokeColorStateList = state.mStrokeColorStateList;
1538            mStrokeWidth = state.mStrokeWidth;
1539            mStrokeDashWidth = state.mStrokeDashWidth;
1540            mStrokeDashGap = state.mStrokeDashGap;
1541            mRadius = state.mRadius;
1542            if (state.mRadiusArray != null) {
1543                mRadiusArray = state.mRadiusArray.clone();
1544            }
1545            if (state.mPadding != null) {
1546                mPadding = new Rect(state.mPadding);
1547            }
1548            mWidth = state.mWidth;
1549            mHeight = state.mHeight;
1550            mInnerRadiusRatio = state.mInnerRadiusRatio;
1551            mThicknessRatio = state.mThicknessRatio;
1552            mInnerRadius = state.mInnerRadius;
1553            mThickness = state.mThickness;
1554            mCenterX = state.mCenterX;
1555            mCenterY = state.mCenterY;
1556            mGradientRadius = state.mGradientRadius;
1557            mGradientRadiusType = state.mGradientRadiusType;
1558            mUseLevel = state.mUseLevel;
1559            mUseLevelForShape = state.mUseLevelForShape;
1560            mOpaque = state.mOpaque;
1561            mThemeAttrs = state.mThemeAttrs;
1562            mAttrSize = state.mAttrSize;
1563            mAttrGradient = state.mAttrGradient;
1564            mAttrSolid = state.mAttrSolid;
1565            mAttrStroke = state.mAttrStroke;
1566            mAttrCorners = state.mAttrCorners;
1567            mAttrPadding = state.mAttrPadding;
1568        }
1569
1570        @Override
1571        public boolean canApplyTheme() {
1572            return mThemeAttrs != null;
1573        }
1574
1575        @Override
1576        public Drawable newDrawable() {
1577            return new GradientDrawable(this, null);
1578        }
1579
1580        @Override
1581        public Drawable newDrawable(Resources res) {
1582            return new GradientDrawable(this, null);
1583        }
1584
1585        @Override
1586        public Drawable newDrawable(Resources res, Theme theme) {
1587            return new GradientDrawable(this, theme);
1588        }
1589
1590        @Override
1591        public int getChangingConfigurations() {
1592            return mChangingConfigurations;
1593        }
1594
1595        public void setShape(int shape) {
1596            mShape = shape;
1597            computeOpacity();
1598        }
1599
1600        public void setGradientType(int gradient) {
1601            mGradient = gradient;
1602        }
1603
1604        public void setGradientCenter(float x, float y) {
1605            mCenterX = x;
1606            mCenterY = y;
1607        }
1608
1609        public void setColors(int[] colors) {
1610            mColors = colors;
1611            mColorStateList = null;
1612            computeOpacity();
1613        }
1614
1615        public void setColorStateList(ColorStateList colorStateList) {
1616            mColors = null;
1617            mColorStateList = colorStateList;
1618            computeOpacity();
1619        }
1620
1621        private void computeOpacity() {
1622            if (mShape != RECTANGLE) {
1623                mOpaque = false;
1624                return;
1625            }
1626
1627            if (mRadius > 0 || mRadiusArray != null) {
1628                mOpaque = false;
1629                return;
1630            }
1631
1632            if (mStrokeWidth > 0) {
1633                if (mStrokeColorStateList != null) {
1634                    if (!mStrokeColorStateList.isOpaque()) {
1635                        mOpaque = false;
1636                        return;
1637                    }
1638                }
1639            }
1640
1641            if (mColorStateList != null && !mColorStateList.isOpaque()) {
1642                mOpaque = false;
1643                return;
1644            }
1645
1646            if (mColors != null) {
1647                for (int i = 0; i < mColors.length; i++) {
1648                    if (!isOpaque(mColors[i])) {
1649                        mOpaque = false;
1650                        return;
1651                    }
1652                }
1653            }
1654
1655            mOpaque = true;
1656        }
1657
1658        private static boolean isOpaque(int color) {
1659            return ((color >> 24) & 0xff) == 0xff;
1660        }
1661
1662        public void setStroke(
1663                int width, ColorStateList colorStateList, float dashWidth, float dashGap) {
1664            mStrokeWidth = width;
1665            mStrokeColorStateList = colorStateList;
1666            mStrokeDashWidth = dashWidth;
1667            mStrokeDashGap = dashGap;
1668            computeOpacity();
1669        }
1670
1671        public void setCornerRadius(float radius) {
1672            if (radius < 0) {
1673                radius = 0;
1674            }
1675            mRadius = radius;
1676            mRadiusArray = null;
1677        }
1678
1679        public void setCornerRadii(float[] radii) {
1680            mRadiusArray = radii;
1681            if (radii == null) {
1682                mRadius = 0;
1683            }
1684        }
1685
1686        public void setSize(int width, int height) {
1687            mWidth = width;
1688            mHeight = height;
1689        }
1690
1691        public void setGradientRadius(float gradientRadius, int type) {
1692            mGradientRadius = gradientRadius;
1693            mGradientRadiusType = type;
1694        }
1695    }
1696
1697    /**
1698     * Creates a new themed GradientDrawable based on the specified constant state.
1699     * <p>
1700     * The resulting drawable is guaranteed to have a new constant state.
1701     *
1702     * @param state Constant state from which the drawable inherits
1703     * @param theme Theme to apply to the drawable
1704     */
1705    private GradientDrawable(GradientState state, Theme theme) {
1706        mGradientState = new GradientState(state);
1707        if (theme != null && state.canApplyTheme()) {
1708            applyTheme(theme);
1709        }
1710
1711        initializeWithState(state);
1712        mRectIsDirty = true;
1713        mMutated = false;
1714    }
1715
1716    private void initializeWithState(GradientState state) {
1717        if (state.mColorStateList != null) {
1718            final int[] currentState = getState();
1719            final int stateColor = state.mColorStateList.getColorForState(currentState, 0);
1720            mFillPaint.setColor(stateColor);
1721        } else if (state.mColors == null) {
1722            // If we don't have a solid color and we don't have a gradient,
1723            // the app is stroking the shape, set the color to the default
1724            // value of state.mSolidColor
1725            mFillPaint.setColor(0);
1726        } else {
1727            // Otherwise, make sure the fill alpha is maxed out.
1728            mFillPaint.setColor(Color.BLACK);
1729        }
1730        mPadding = state.mPadding;
1731        if (state.mStrokeWidth >= 0) {
1732            mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1733            mStrokePaint.setStyle(Paint.Style.STROKE);
1734            mStrokePaint.setStrokeWidth(state.mStrokeWidth);
1735            if (state.mStrokeColorStateList != null) {
1736                final int[] currentState = getState();
1737                final int strokeStateColor = state.mStrokeColorStateList.getColorForState(
1738                        currentState, 0);
1739                mStrokePaint.setColor(strokeStateColor);
1740            }
1741
1742            if (state.mStrokeDashWidth != 0.0f) {
1743                DashPathEffect e = new DashPathEffect(
1744                        new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
1745                mStrokePaint.setPathEffect(e);
1746            }
1747        }
1748    }
1749}
1750