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