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