GradientDrawable.java revision f4c068b72e2dee2e6944488ef00b64c93217d7e8
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 boolean getDither() {
826        return mGradientState.mDither;
827    }
828
829    @Override
830    public ColorFilter getColorFilter() {
831        return mColorFilter;
832    }
833
834    @Override
835    public void setColorFilter(ColorFilter cf) {
836        if (cf != mColorFilter) {
837            mColorFilter = cf;
838            invalidateSelf();
839        }
840    }
841
842    @Override
843    public void setTintList(ColorStateList tint) {
844        mGradientState.mTint = tint;
845        mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode);
846        invalidateSelf();
847    }
848
849    @Override
850    public void setTintMode(PorterDuff.Mode tintMode) {
851        mGradientState.mTintMode = tintMode;
852        mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode);
853        invalidateSelf();
854    }
855
856    @Override
857    public int getOpacity() {
858        return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ?
859                PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
860    }
861
862    @Override
863    protected void onBoundsChange(Rect r) {
864        super.onBoundsChange(r);
865        mRingPath = null;
866        mPathIsDirty = true;
867        mGradientIsDirty = true;
868    }
869
870    @Override
871    protected boolean onLevelChange(int level) {
872        super.onLevelChange(level);
873        mGradientIsDirty = true;
874        mPathIsDirty = true;
875        invalidateSelf();
876        return true;
877    }
878
879    /**
880     * This checks mGradientIsDirty, and if it is true, recomputes both our drawing
881     * rectangle (mRect) and the gradient itself, since it depends on our
882     * rectangle too.
883     * @return true if the resulting rectangle is not empty, false otherwise
884     */
885    private boolean ensureValidRect() {
886        if (mGradientIsDirty) {
887            mGradientIsDirty = false;
888
889            Rect bounds = getBounds();
890            float inset = 0;
891
892            if (mStrokePaint != null) {
893                inset = mStrokePaint.getStrokeWidth() * 0.5f;
894            }
895
896            final GradientState st = mGradientState;
897
898            mRect.set(bounds.left + inset, bounds.top + inset,
899                      bounds.right - inset, bounds.bottom - inset);
900
901            final int[] colors = st.mColors;
902            if (colors != null) {
903                RectF r = mRect;
904                float x0, x1, y0, y1;
905
906                if (st.mGradient == LINEAR_GRADIENT) {
907                    final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f;
908                    switch (st.mOrientation) {
909                    case TOP_BOTTOM:
910                        x0 = r.left;            y0 = r.top;
911                        x1 = x0;                y1 = level * r.bottom;
912                        break;
913                    case TR_BL:
914                        x0 = r.right;           y0 = r.top;
915                        x1 = level * r.left;    y1 = level * r.bottom;
916                        break;
917                    case RIGHT_LEFT:
918                        x0 = r.right;           y0 = r.top;
919                        x1 = level * r.left;    y1 = y0;
920                        break;
921                    case BR_TL:
922                        x0 = r.right;           y0 = r.bottom;
923                        x1 = level * r.left;    y1 = level * r.top;
924                        break;
925                    case BOTTOM_TOP:
926                        x0 = r.left;            y0 = r.bottom;
927                        x1 = x0;                y1 = level * r.top;
928                        break;
929                    case BL_TR:
930                        x0 = r.left;            y0 = r.bottom;
931                        x1 = level * r.right;   y1 = level * r.top;
932                        break;
933                    case LEFT_RIGHT:
934                        x0 = r.left;            y0 = r.top;
935                        x1 = level * r.right;   y1 = y0;
936                        break;
937                    default:/* TL_BR */
938                        x0 = r.left;            y0 = r.top;
939                        x1 = level * r.right;   y1 = level * r.bottom;
940                        break;
941                    }
942
943                    mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
944                            colors, st.mPositions, Shader.TileMode.CLAMP));
945                } else if (st.mGradient == RADIAL_GRADIENT) {
946                    x0 = r.left + (r.right - r.left) * st.mCenterX;
947                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
948
949                    float radius = st.mGradientRadius;
950                    if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) {
951                        // Fall back to parent width or height if intrinsic
952                        // size is not specified.
953                        final float width = st.mWidth >= 0 ? st.mWidth : r.width();
954                        final float height = st.mHeight >= 0 ? st.mHeight : r.height();
955                        radius *= Math.min(width, height);
956                    } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) {
957                        radius *= Math.min(r.width(), r.height());
958                    }
959
960                    if (st.mUseLevel) {
961                        radius *= getLevel() / 10000.0f;
962                    }
963
964                    mGradientRadius = radius;
965
966                    if (radius <= 0) {
967                        // We can't have a shader with non-positive radius, so
968                        // let's have a very, very small radius.
969                        radius = 0.001f;
970                    }
971
972                    mFillPaint.setShader(new RadialGradient(
973                            x0, y0, radius, colors, null, Shader.TileMode.CLAMP));
974                } else if (st.mGradient == SWEEP_GRADIENT) {
975                    x0 = r.left + (r.right - r.left) * st.mCenterX;
976                    y0 = r.top + (r.bottom - r.top) * st.mCenterY;
977
978                    int[] tempColors = colors;
979                    float[] tempPositions = null;
980
981                    if (st.mUseLevel) {
982                        tempColors = st.mTempColors;
983                        final int length = colors.length;
984                        if (tempColors == null || tempColors.length != length + 1) {
985                            tempColors = st.mTempColors = new int[length + 1];
986                        }
987                        System.arraycopy(colors, 0, tempColors, 0, length);
988                        tempColors[length] = colors[length - 1];
989
990                        tempPositions = st.mTempPositions;
991                        final float fraction = 1.0f / (length - 1);
992                        if (tempPositions == null || tempPositions.length != length + 1) {
993                            tempPositions = st.mTempPositions = new float[length + 1];
994                        }
995
996                        final float level = getLevel() / 10000.0f;
997                        for (int i = 0; i < length; i++) {
998                            tempPositions[i] = i * fraction * level;
999                        }
1000                        tempPositions[length] = 1.0f;
1001
1002                    }
1003                    mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
1004                }
1005
1006                // If we don't have a solid color, the alpha channel must be
1007                // maxed out so that alpha modulation works correctly.
1008                if (st.mColorStateList == null) {
1009                    mFillPaint.setColor(Color.BLACK);
1010                }
1011            }
1012        }
1013        return !mRect.isEmpty();
1014    }
1015
1016    @Override
1017    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
1018            throws XmlPullParserException, IOException {
1019        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable);
1020        super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible);
1021        updateStateFromTypedArray(a);
1022        a.recycle();
1023
1024        inflateChildElements(r, parser, attrs, theme);
1025
1026        updateLocalState(r);
1027    }
1028
1029    @Override
1030    public void applyTheme(Theme t) {
1031        super.applyTheme(t);
1032
1033        final GradientState state = mGradientState;
1034        if (state == null) {
1035            return;
1036        }
1037
1038        if (state.mThemeAttrs != null) {
1039            final TypedArray a = t.resolveAttributes(
1040                    state.mThemeAttrs, R.styleable.GradientDrawable);
1041            updateStateFromTypedArray(a);
1042            a.recycle();
1043        }
1044
1045        if (state.mTint != null && state.mTint.canApplyTheme()) {
1046            state.mTint.applyTheme(t);
1047        }
1048
1049        if (state.mColorStateList != null && state.mColorStateList.canApplyTheme()) {
1050            state.mColorStateList.applyTheme(t);
1051        }
1052
1053        if (state.mStrokeColorStateList != null && state.mStrokeColorStateList.canApplyTheme()) {
1054            state.mStrokeColorStateList.applyTheme(t);
1055        }
1056
1057        applyThemeChildElements(t);
1058
1059        updateLocalState(t.getResources());
1060    }
1061
1062    /**
1063     * Updates the constant state from the values in the typed array.
1064     */
1065    private void updateStateFromTypedArray(TypedArray a) {
1066        final GradientState state = mGradientState;
1067
1068        // Account for any configuration changes.
1069        state.mChangingConfigurations |= a.getChangingConfigurations();
1070
1071        // Extract the theme attributes, if any.
1072        state.mThemeAttrs = a.extractThemeAttrs();
1073
1074        state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape);
1075        state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither);
1076
1077        if (state.mShape == RING) {
1078            state.mInnerRadius = a.getDimensionPixelSize(
1079                    R.styleable.GradientDrawable_innerRadius, state.mInnerRadius);
1080
1081            if (state.mInnerRadius == -1) {
1082                state.mInnerRadiusRatio = a.getFloat(
1083                        R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio);
1084            }
1085
1086            state.mThickness = a.getDimensionPixelSize(
1087                    R.styleable.GradientDrawable_thickness, state.mThickness);
1088
1089            if (state.mThickness == -1) {
1090                state.mThicknessRatio = a.getFloat(
1091                        R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio);
1092            }
1093
1094            state.mUseLevelForShape = a.getBoolean(
1095                    R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape);
1096        }
1097
1098        final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1);
1099        if (tintMode != -1) {
1100            state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN);
1101        }
1102
1103        final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint);
1104        if (tint != null) {
1105            state.mTint = tint;
1106        }
1107    }
1108
1109    @Override
1110    public boolean canApplyTheme() {
1111        return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme();
1112    }
1113
1114    private void applyThemeChildElements(Theme t) {
1115        final GradientState st = mGradientState;
1116
1117        if (st.mAttrSize != null) {
1118            final TypedArray a = t.resolveAttributes(
1119                    st.mAttrSize, R.styleable.GradientDrawableSize);
1120            updateGradientDrawableSize(a);
1121            a.recycle();
1122        }
1123
1124        if (st.mAttrGradient != null) {
1125            final TypedArray a = t.resolveAttributes(
1126                    st.mAttrGradient, R.styleable.GradientDrawableGradient);
1127            try {
1128                updateGradientDrawableGradient(t.getResources(), a);
1129            } catch (XmlPullParserException e) {
1130                throw new RuntimeException(e);
1131            } finally {
1132                a.recycle();
1133            }
1134        }
1135
1136        if (st.mAttrSolid != null) {
1137            final TypedArray a = t.resolveAttributes(
1138                    st.mAttrSolid, R.styleable.GradientDrawableSolid);
1139            updateGradientDrawableSolid(a);
1140            a.recycle();
1141        }
1142
1143        if (st.mAttrStroke != null) {
1144            final TypedArray a = t.resolveAttributes(
1145                    st.mAttrStroke, R.styleable.GradientDrawableStroke);
1146            updateGradientDrawableStroke(a);
1147            a.recycle();
1148        }
1149
1150        if (st.mAttrCorners != null) {
1151            final TypedArray a = t.resolveAttributes(
1152                    st.mAttrCorners, R.styleable.DrawableCorners);
1153            updateDrawableCorners(a);
1154            a.recycle();
1155        }
1156
1157        if (st.mAttrPadding != null) {
1158            final TypedArray a = t.resolveAttributes(
1159                    st.mAttrPadding, R.styleable.GradientDrawablePadding);
1160            updateGradientDrawablePadding(a);
1161            a.recycle();
1162        }
1163    }
1164
1165    private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
1166            Theme theme) throws XmlPullParserException, IOException {
1167        TypedArray a;
1168        int type;
1169
1170        final int innerDepth = parser.getDepth() + 1;
1171        int depth;
1172        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
1173               && ((depth=parser.getDepth()) >= innerDepth
1174                       || type != XmlPullParser.END_TAG)) {
1175            if (type != XmlPullParser.START_TAG) {
1176                continue;
1177            }
1178
1179            if (depth > innerDepth) {
1180                continue;
1181            }
1182
1183            String name = parser.getName();
1184
1185            if (name.equals("size")) {
1186                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize);
1187                updateGradientDrawableSize(a);
1188                a.recycle();
1189            } else if (name.equals("gradient")) {
1190                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient);
1191                updateGradientDrawableGradient(r, a);
1192                a.recycle();
1193            } else if (name.equals("solid")) {
1194                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid);
1195                updateGradientDrawableSolid(a);
1196                a.recycle();
1197            } else if (name.equals("stroke")) {
1198                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke);
1199                updateGradientDrawableStroke(a);
1200                a.recycle();
1201            } else if (name.equals("corners")) {
1202                a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners);
1203                updateDrawableCorners(a);
1204                a.recycle();
1205            } else if (name.equals("padding")) {
1206                a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding);
1207                updateGradientDrawablePadding(a);
1208                a.recycle();
1209            } else {
1210                Log.w("drawable", "Bad element under <shape>: " + name);
1211            }
1212        }
1213    }
1214
1215    private void updateGradientDrawablePadding(TypedArray a) {
1216        final GradientState st = mGradientState;
1217
1218        // Account for any configuration changes.
1219        st.mChangingConfigurations |= a.getChangingConfigurations();
1220
1221        // Extract the theme attributes, if any.
1222        st.mAttrPadding = a.extractThemeAttrs();
1223
1224        if (st.mPadding == null) {
1225            st.mPadding = new Rect();
1226        }
1227
1228        final Rect pad = st.mPadding;
1229        pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left),
1230                a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top),
1231                a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right),
1232                a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom));
1233        mPadding = pad;
1234    }
1235
1236    private void updateDrawableCorners(TypedArray a) {
1237        final GradientState st = mGradientState;
1238
1239        // Account for any configuration changes.
1240        st.mChangingConfigurations |= a.getChangingConfigurations();
1241
1242        // Extract the theme attributes, if any.
1243        st.mAttrCorners = a.extractThemeAttrs();
1244
1245        final int radius = a.getDimensionPixelSize(
1246                R.styleable.DrawableCorners_radius, (int) st.mRadius);
1247        setCornerRadius(radius);
1248
1249        // TODO: Update these to be themeable.
1250        final int topLeftRadius = a.getDimensionPixelSize(
1251                R.styleable.DrawableCorners_topLeftRadius, radius);
1252        final int topRightRadius = a.getDimensionPixelSize(
1253                R.styleable.DrawableCorners_topRightRadius, radius);
1254        final int bottomLeftRadius = a.getDimensionPixelSize(
1255                R.styleable.DrawableCorners_bottomLeftRadius, radius);
1256        final int bottomRightRadius = a.getDimensionPixelSize(
1257                R.styleable.DrawableCorners_bottomRightRadius, radius);
1258        if (topLeftRadius != radius || topRightRadius != radius ||
1259                bottomLeftRadius != radius || bottomRightRadius != radius) {
1260            // The corner radii are specified in clockwise order (see Path.addRoundRect())
1261            setCornerRadii(new float[] {
1262                    topLeftRadius, topLeftRadius,
1263                    topRightRadius, topRightRadius,
1264                    bottomRightRadius, bottomRightRadius,
1265                    bottomLeftRadius, bottomLeftRadius
1266            });
1267        }
1268    }
1269
1270    private void updateGradientDrawableStroke(TypedArray a) {
1271        final GradientState st = mGradientState;
1272
1273        // Account for any configuration changes.
1274        st.mChangingConfigurations |= a.getChangingConfigurations();
1275
1276        // Extract the theme attributes, if any.
1277        st.mAttrStroke = a.extractThemeAttrs();
1278
1279        // We have an explicit stroke defined, so the default stroke width
1280        // must be at least 0 or the current stroke width.
1281        final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth);
1282        final int width = a.getDimensionPixelSize(
1283                R.styleable.GradientDrawableStroke_width, defaultStrokeWidth);
1284        final float dashWidth = a.getDimension(
1285                R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth);
1286
1287        ColorStateList colorStateList = a.getColorStateList(
1288                R.styleable.GradientDrawableStroke_color);
1289        if (colorStateList == null) {
1290            colorStateList = st.mStrokeColorStateList;
1291        }
1292
1293        if (dashWidth != 0.0f) {
1294            final float dashGap = a.getDimension(
1295                    R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap);
1296            setStroke(width, colorStateList, dashWidth, dashGap);
1297        } else {
1298            setStroke(width, colorStateList);
1299        }
1300    }
1301
1302    private void updateGradientDrawableSolid(TypedArray a) {
1303        final GradientState st = mGradientState;
1304
1305        // Account for any configuration changes.
1306        st.mChangingConfigurations |= a.getChangingConfigurations();
1307
1308        // Extract the theme attributes, if any.
1309        st.mAttrSolid = a.extractThemeAttrs();
1310
1311        final ColorStateList colorStateList = a.getColorStateList(
1312                R.styleable.GradientDrawableSolid_color);
1313        if (colorStateList != null) {
1314            setColor(colorStateList);
1315        }
1316    }
1317
1318    private void updateGradientDrawableGradient(Resources r, TypedArray a)
1319            throws XmlPullParserException {
1320        final GradientState st = mGradientState;
1321
1322        // Account for any configuration changes.
1323        st.mChangingConfigurations |= a.getChangingConfigurations();
1324
1325        // Extract the theme attributes, if any.
1326        st.mAttrGradient = a.extractThemeAttrs();
1327
1328        st.mCenterX = getFloatOrFraction(
1329                a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX);
1330        st.mCenterY = getFloatOrFraction(
1331                a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY);
1332        st.mUseLevel = a.getBoolean(
1333                R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel);
1334        st.mGradient = a.getInt(
1335                R.styleable.GradientDrawableGradient_type, st.mGradient);
1336
1337        // TODO: Update these to be themeable.
1338        final int startColor = a.getColor(
1339                R.styleable.GradientDrawableGradient_startColor, 0);
1340        final boolean hasCenterColor = a.hasValue(
1341                R.styleable.GradientDrawableGradient_centerColor);
1342        final int centerColor = a.getColor(
1343                R.styleable.GradientDrawableGradient_centerColor, 0);
1344        final int endColor = a.getColor(
1345                R.styleable.GradientDrawableGradient_endColor, 0);
1346
1347        if (hasCenterColor) {
1348            st.mColors = new int[3];
1349            st.mColors[0] = startColor;
1350            st.mColors[1] = centerColor;
1351            st.mColors[2] = endColor;
1352
1353            st.mPositions = new float[3];
1354            st.mPositions[0] = 0.0f;
1355            // Since 0.5f is default value, try to take the one that isn't 0.5f
1356            st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY;
1357            st.mPositions[2] = 1f;
1358        } else {
1359            st.mColors = new int[2];
1360            st.mColors[0] = startColor;
1361            st.mColors[1] = endColor;
1362        }
1363
1364        if (st.mGradient == LINEAR_GRADIENT) {
1365            int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle);
1366            angle %= 360;
1367
1368            if (angle % 45 != 0) {
1369                throw new XmlPullParserException(a.getPositionDescription()
1370                        + "<gradient> tag requires 'angle' attribute to "
1371                        + "be a multiple of 45");
1372            }
1373
1374            st.mAngle = angle;
1375
1376            switch (angle) {
1377                case 0:
1378                    st.mOrientation = Orientation.LEFT_RIGHT;
1379                    break;
1380                case 45:
1381                    st.mOrientation = Orientation.BL_TR;
1382                    break;
1383                case 90:
1384                    st.mOrientation = Orientation.BOTTOM_TOP;
1385                    break;
1386                case 135:
1387                    st.mOrientation = Orientation.BR_TL;
1388                    break;
1389                case 180:
1390                    st.mOrientation = Orientation.RIGHT_LEFT;
1391                    break;
1392                case 225:
1393                    st.mOrientation = Orientation.TR_BL;
1394                    break;
1395                case 270:
1396                    st.mOrientation = Orientation.TOP_BOTTOM;
1397                    break;
1398                case 315:
1399                    st.mOrientation = Orientation.TL_BR;
1400                    break;
1401            }
1402        } else {
1403            final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius);
1404            if (tv != null) {
1405                final float radius;
1406                final int radiusType;
1407                if (tv.type == TypedValue.TYPE_FRACTION) {
1408                    radius = tv.getFraction(1.0f, 1.0f);
1409
1410                    final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT)
1411                            & TypedValue.COMPLEX_UNIT_MASK;
1412                    if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) {
1413                        radiusType = RADIUS_TYPE_FRACTION_PARENT;
1414                    } else {
1415                        radiusType = RADIUS_TYPE_FRACTION;
1416                    }
1417                } else if (tv.type == TypedValue.TYPE_DIMENSION) {
1418                    radius = tv.getDimension(r.getDisplayMetrics());
1419                    radiusType = RADIUS_TYPE_PIXELS;
1420                } else {
1421                    radius = tv.getFloat();
1422                    radiusType = RADIUS_TYPE_PIXELS;
1423                }
1424
1425                st.mGradientRadius = radius;
1426                st.mGradientRadiusType = radiusType;
1427            } else if (st.mGradient == RADIAL_GRADIENT) {
1428                throw new XmlPullParserException(
1429                        a.getPositionDescription()
1430                        + "<gradient> tag requires 'gradientRadius' "
1431                        + "attribute with radial type");
1432            }
1433        }
1434    }
1435
1436    private void updateGradientDrawableSize(TypedArray a) {
1437        final GradientState st = mGradientState;
1438
1439        // Account for any configuration changes.
1440        st.mChangingConfigurations |= a.getChangingConfigurations();
1441
1442        // Extract the theme attributes, if any.
1443        st.mAttrSize = a.extractThemeAttrs();
1444
1445        st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth);
1446        st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight);
1447    }
1448
1449    private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) {
1450        TypedValue tv = a.peekValue(index);
1451        float v = defaultValue;
1452        if (tv != null) {
1453            boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION;
1454            v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
1455        }
1456        return v;
1457    }
1458
1459    @Override
1460    public int getIntrinsicWidth() {
1461        return mGradientState.mWidth;
1462    }
1463
1464    @Override
1465    public int getIntrinsicHeight() {
1466        return mGradientState.mHeight;
1467    }
1468
1469    @Override
1470    public ConstantState getConstantState() {
1471        mGradientState.mChangingConfigurations = getChangingConfigurations();
1472        return mGradientState;
1473    }
1474
1475    private boolean isOpaqueForState() {
1476        if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null
1477                && !isOpaque(mStrokePaint.getColor())) {
1478            return false;
1479        }
1480
1481        if (!isOpaque(mFillPaint.getColor())) {
1482            return false;
1483        }
1484
1485        return true;
1486    }
1487
1488    @Override
1489    public void getOutline(Outline outline) {
1490        final GradientState st = mGradientState;
1491        final Rect bounds = getBounds();
1492        // only report non-zero alpha if shape being drawn is opaque
1493        outline.setAlpha(st.mOpaqueOverShape && isOpaqueForState() ? (mAlpha / 255.0f) : 0.0f);
1494
1495        switch (st.mShape) {
1496            case RECTANGLE:
1497                if (st.mRadiusArray != null) {
1498                    buildPathIfDirty();
1499                    outline.setConvexPath(mPath);
1500                    return;
1501                }
1502
1503                float rad = 0;
1504                if (st.mRadius > 0.0f) {
1505                    // clamp the radius based on width & height, matching behavior in draw()
1506                    rad = Math.min(st.mRadius,
1507                            Math.min(bounds.width(), bounds.height()) * 0.5f);
1508                }
1509                outline.setRoundRect(bounds, rad);
1510                return;
1511            case OVAL:
1512                outline.setOval(bounds);
1513                return;
1514            case LINE:
1515                // Hairlines (0-width stroke) must have a non-empty outline for
1516                // shadows to draw correctly, so we'll use a very small width.
1517                final float halfStrokeWidth = mStrokePaint == null ?
1518                        0.0001f : mStrokePaint.getStrokeWidth() * 0.5f;
1519                final float centerY = bounds.centerY();
1520                final int top = (int) Math.floor(centerY - halfStrokeWidth);
1521                final int bottom = (int) Math.ceil(centerY + halfStrokeWidth);
1522
1523                outline.setRect(bounds.left, top, bounds.right, bottom);
1524                return;
1525            default:
1526                // TODO: support more complex shapes
1527        }
1528    }
1529
1530    @Override
1531    public Drawable mutate() {
1532        if (!mMutated && super.mutate() == this) {
1533            mGradientState = new GradientState(mGradientState);
1534            updateLocalState(null);
1535            mMutated = true;
1536        }
1537        return this;
1538    }
1539
1540    /**
1541     * @hide
1542     */
1543    public void clearMutated() {
1544        super.clearMutated();
1545        mMutated = false;
1546    }
1547
1548    final static class GradientState extends ConstantState {
1549        public int mChangingConfigurations;
1550        public int mShape = RECTANGLE;
1551        public int mGradient = LINEAR_GRADIENT;
1552        public int mAngle = 0;
1553        public Orientation mOrientation;
1554        public ColorStateList mColorStateList;
1555        public ColorStateList mStrokeColorStateList;
1556        public int[] mColors;
1557        public int[] mTempColors; // no need to copy
1558        public float[] mTempPositions; // no need to copy
1559        public float[] mPositions;
1560        public int mStrokeWidth = -1; // if >= 0 use stroking.
1561        public float mStrokeDashWidth = 0.0f;
1562        public float mStrokeDashGap = 0.0f;
1563        public float mRadius = 0.0f; // use this if mRadiusArray is null
1564        public float[] mRadiusArray = null;
1565        public Rect mPadding = null;
1566        public int mWidth = -1;
1567        public int mHeight = -1;
1568        public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO;
1569        public float mThicknessRatio = DEFAULT_THICKNESS_RATIO;
1570        public int mInnerRadius = -1;
1571        public int mThickness = -1;
1572        public boolean mDither = false;
1573
1574        float mCenterX = 0.5f;
1575        float mCenterY = 0.5f;
1576        float mGradientRadius = 0.5f;
1577        int mGradientRadiusType = RADIUS_TYPE_PIXELS;
1578        boolean mUseLevel = false;
1579        boolean mUseLevelForShape = true;
1580
1581        boolean mOpaqueOverBounds;
1582        boolean mOpaqueOverShape;
1583
1584        ColorStateList mTint = null;
1585        PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
1586
1587        int[] mThemeAttrs;
1588        int[] mAttrSize;
1589        int[] mAttrGradient;
1590        int[] mAttrSolid;
1591        int[] mAttrStroke;
1592        int[] mAttrCorners;
1593        int[] mAttrPadding;
1594
1595        public GradientState(Orientation orientation, int[] colors) {
1596            mOrientation = orientation;
1597            setColors(colors);
1598        }
1599
1600        public GradientState(GradientState state) {
1601            mChangingConfigurations = state.mChangingConfigurations;
1602            mShape = state.mShape;
1603            mGradient = state.mGradient;
1604            mAngle = state.mAngle;
1605            mOrientation = state.mOrientation;
1606            mColorStateList = state.mColorStateList;
1607            if (state.mColors != null) {
1608                mColors = state.mColors.clone();
1609            }
1610            if (state.mPositions != null) {
1611                mPositions = state.mPositions.clone();
1612            }
1613            mStrokeColorStateList = state.mStrokeColorStateList;
1614            mStrokeWidth = state.mStrokeWidth;
1615            mStrokeDashWidth = state.mStrokeDashWidth;
1616            mStrokeDashGap = state.mStrokeDashGap;
1617            mRadius = state.mRadius;
1618            if (state.mRadiusArray != null) {
1619                mRadiusArray = state.mRadiusArray.clone();
1620            }
1621            if (state.mPadding != null) {
1622                mPadding = new Rect(state.mPadding);
1623            }
1624            mWidth = state.mWidth;
1625            mHeight = state.mHeight;
1626            mInnerRadiusRatio = state.mInnerRadiusRatio;
1627            mThicknessRatio = state.mThicknessRatio;
1628            mInnerRadius = state.mInnerRadius;
1629            mThickness = state.mThickness;
1630            mDither = state.mDither;
1631            mCenterX = state.mCenterX;
1632            mCenterY = state.mCenterY;
1633            mGradientRadius = state.mGradientRadius;
1634            mGradientRadiusType = state.mGradientRadiusType;
1635            mUseLevel = state.mUseLevel;
1636            mUseLevelForShape = state.mUseLevelForShape;
1637            mOpaqueOverBounds = state.mOpaqueOverBounds;
1638            mOpaqueOverShape = state.mOpaqueOverShape;
1639            mTint = state.mTint;
1640            mTintMode = state.mTintMode;
1641            mThemeAttrs = state.mThemeAttrs;
1642            mAttrSize = state.mAttrSize;
1643            mAttrGradient = state.mAttrGradient;
1644            mAttrSolid = state.mAttrSolid;
1645            mAttrStroke = state.mAttrStroke;
1646            mAttrCorners = state.mAttrCorners;
1647            mAttrPadding = state.mAttrPadding;
1648        }
1649
1650        @Override
1651        public boolean canApplyTheme() {
1652            return mThemeAttrs != null
1653                    || mAttrSize != null || mAttrGradient != null
1654                    || mAttrSolid != null || mAttrStroke != null
1655                    || mAttrCorners != null || mAttrPadding != null
1656                    || (mTint != null && mTint.canApplyTheme())
1657                    || (mStrokeColorStateList != null && mStrokeColorStateList.canApplyTheme())
1658                    || (mColorStateList != null && mColorStateList.canApplyTheme())
1659                    || super.canApplyTheme();
1660        }
1661
1662        @Override
1663        public Drawable newDrawable() {
1664            return new GradientDrawable(this, null);
1665        }
1666
1667        @Override
1668        public Drawable newDrawable(Resources res) {
1669            return new GradientDrawable(this, res);
1670        }
1671
1672        @Override
1673        public int getChangingConfigurations() {
1674            return mChangingConfigurations;
1675        }
1676
1677        public void setShape(int shape) {
1678            mShape = shape;
1679            computeOpacity();
1680        }
1681
1682        public void setGradientType(int gradient) {
1683            mGradient = gradient;
1684        }
1685
1686        public void setGradientCenter(float x, float y) {
1687            mCenterX = x;
1688            mCenterY = y;
1689        }
1690
1691        public void setColors(int[] colors) {
1692            mColors = colors;
1693            mColorStateList = null;
1694            computeOpacity();
1695        }
1696
1697        public void setColorStateList(ColorStateList colorStateList) {
1698            mColors = null;
1699            mColorStateList = colorStateList;
1700            computeOpacity();
1701        }
1702
1703        private void computeOpacity() {
1704            mOpaqueOverBounds = false;
1705            mOpaqueOverShape = false;
1706
1707            if (mColors != null) {
1708                for (int i = 0; i < mColors.length; i++) {
1709                    if (!isOpaque(mColors[i])) {
1710                        return;
1711                    }
1712                }
1713            }
1714
1715            // An unfilled shape is not opaque over bounds or shape
1716            if (mColors == null && mColorStateList == null) {
1717                return;
1718            }
1719
1720            // Colors are opaque, so opaqueOverShape=true,
1721            mOpaqueOverShape = true;
1722            // and opaqueOverBounds=true if shape fills bounds
1723            mOpaqueOverBounds = mShape == RECTANGLE
1724                    && mRadius <= 0
1725                    && mRadiusArray == null;
1726        }
1727
1728        public void setStroke(
1729                int width, ColorStateList colorStateList, float dashWidth, float dashGap) {
1730            mStrokeWidth = width;
1731            mStrokeColorStateList = colorStateList;
1732            mStrokeDashWidth = dashWidth;
1733            mStrokeDashGap = dashGap;
1734            computeOpacity();
1735        }
1736
1737        public void setCornerRadius(float radius) {
1738            if (radius < 0) {
1739                radius = 0;
1740            }
1741            mRadius = radius;
1742            mRadiusArray = null;
1743        }
1744
1745        public void setCornerRadii(float[] radii) {
1746            mRadiusArray = radii;
1747            if (radii == null) {
1748                mRadius = 0;
1749            }
1750        }
1751
1752        public void setSize(int width, int height) {
1753            mWidth = width;
1754            mHeight = height;
1755        }
1756
1757        public void setGradientRadius(float gradientRadius, int type) {
1758            mGradientRadius = gradientRadius;
1759            mGradientRadiusType = type;
1760        }
1761    }
1762
1763    static boolean isOpaque(int color) {
1764        return ((color >> 24) & 0xff) == 0xff;
1765    }
1766
1767    /**
1768     * Creates a new themed GradientDrawable based on the specified constant state.
1769     * <p>
1770     * The resulting drawable is guaranteed to have a new constant state.
1771     *
1772     * @param state Constant state from which the drawable inherits
1773     */
1774    private GradientDrawable(GradientState state, Resources res) {
1775        mGradientState = state;
1776
1777        updateLocalState(res);
1778    }
1779
1780    private void updateLocalState(Resources res) {
1781        final GradientState state = mGradientState;
1782
1783        if (state.mColorStateList != null) {
1784            final int[] currentState = getState();
1785            final int stateColor = state.mColorStateList.getColorForState(currentState, 0);
1786            mFillPaint.setColor(stateColor);
1787        } else if (state.mColors == null) {
1788            // If we don't have a solid color and we don't have a gradient,
1789            // the app is stroking the shape, set the color to the default
1790            // value of state.mSolidColor
1791            mFillPaint.setColor(0);
1792        } else {
1793            // Otherwise, make sure the fill alpha is maxed out.
1794            mFillPaint.setColor(Color.BLACK);
1795        }
1796
1797        mPadding = state.mPadding;
1798
1799        if (state.mStrokeWidth >= 0) {
1800            mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1801            mStrokePaint.setStyle(Paint.Style.STROKE);
1802            mStrokePaint.setStrokeWidth(state.mStrokeWidth);
1803
1804            if (state.mStrokeColorStateList != null) {
1805                final int[] currentState = getState();
1806                final int strokeStateColor = state.mStrokeColorStateList.getColorForState(
1807                        currentState, 0);
1808                mStrokePaint.setColor(strokeStateColor);
1809            }
1810
1811            if (state.mStrokeDashWidth != 0.0f) {
1812                final DashPathEffect e = new DashPathEffect(
1813                        new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
1814                mStrokePaint.setPathEffect(e);
1815            }
1816        }
1817
1818        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
1819        mGradientIsDirty = true;
1820
1821        state.computeOpacity();
1822    }
1823}
1824