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