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