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