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