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