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