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