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 
17 package android.graphics.drawable;
18 
19 import android.annotation.ColorInt;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.pm.ActivityInfo.Config;
24 import android.content.res.ColorStateList;
25 import android.content.res.Resources;
26 import android.content.res.Resources.Theme;
27 import android.content.res.TypedArray;
28 import android.graphics.Canvas;
29 import android.graphics.Color;
30 import android.graphics.ColorFilter;
31 import android.graphics.DashPathEffect;
32 import android.graphics.Insets;
33 import android.graphics.LinearGradient;
34 import android.graphics.Outline;
35 import android.graphics.Paint;
36 import android.graphics.Path;
37 import android.graphics.PixelFormat;
38 import android.graphics.PorterDuff;
39 import android.graphics.PorterDuffColorFilter;
40 import android.graphics.RadialGradient;
41 import android.graphics.Rect;
42 import android.graphics.RectF;
43 import android.graphics.Shader;
44 import android.graphics.SweepGradient;
45 import android.util.AttributeSet;
46 import android.util.DisplayMetrics;
47 import android.util.Log;
48 import android.util.TypedValue;
49 
50 import com.android.internal.R;
51 
52 import org.xmlpull.v1.XmlPullParser;
53 import org.xmlpull.v1.XmlPullParserException;
54 
55 import java.io.IOException;
56 import java.lang.annotation.Retention;
57 import 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  */
94 public 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 
GradientDrawable()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      */
GradientDrawable(Orientation orientation, @ColorInt int[] colors)208     public GradientDrawable(Orientation orientation, @ColorInt int[] colors) {
209         this(new GradientState(orientation, colors), null);
210     }
211 
212     @Override
getPadding(Rect padding)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      */
setCornerRadii(@ullable float[] radii)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
getCornerRadii()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      */
setCornerRadius(float radius)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      */
getCornerRadius()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      */
setStroke(int width, @ColorInt int color)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      */
setStroke(int width, ColorStateList colorStateList)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      */
setStroke(int width, @ColorInt int color, float dashWidth, float dashGap)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      */
setStroke( int width, ColorStateList colorStateList, float dashWidth, float dashGap)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 
setStrokeInternal(int width, int color, float dashWidth, float dashGap)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      */
setSize(int width, int height)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      */
setShape(@hape int shape)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
getShape()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      */
setGradientType(@radientType int gradient)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
getGradientType()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      */
setGradientCenter(float x, float y)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      */
getGradientCenterX()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      */
getGradientCenterY()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      */
setGradientRadius(float gradientRadius)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      */
getGradientRadius()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      */
setUseLevel(boolean useLevel)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      */
getUseLevel()585     public boolean getUseLevel() {
586         return mGradientState.mUseLevel;
587     }
588 
modulateAlpha(int alpha)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      */
getOrientation()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      */
setOrientation(Orientation orientation)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      */
setColors(@olorInt int[] colors)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
getColors()650     public int[] getColors() {
651         return mGradientState.mGradientColors == null ?
652                 null : mGradientState.mGradientColors.clone();
653     }
654 
655     @Override
draw(Canvas canvas)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(
a.getPositionDescription()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