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