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