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