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