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