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