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