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