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