[go: nahoru, domu]

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