Home | History | Annotate | Download | only in drawable
      1 /*
      2  * Copyright (C) 2006 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.graphics.drawable;
     18 
     19 import android.annotation.ColorInt;
     20 import android.annotation.IntDef;
     21 import android.annotation.NonNull;
     22 import android.annotation.Nullable;
     23 import android.content.pm.ActivityInfo.Config;
     24 import android.content.res.ColorStateList;
     25 import android.content.res.Resources;
     26 import android.content.res.Resources.Theme;
     27 import android.content.res.TypedArray;
     28 import android.graphics.Canvas;
     29 import android.graphics.Color;
     30 import android.graphics.ColorFilter;
     31 import android.graphics.DashPathEffect;
     32 import android.graphics.Insets;
     33 import android.graphics.LinearGradient;
     34 import android.graphics.Outline;
     35 import android.graphics.Paint;
     36 import android.graphics.Path;
     37 import android.graphics.PixelFormat;
     38 import android.graphics.PorterDuff;
     39 import android.graphics.PorterDuffColorFilter;
     40 import android.graphics.RadialGradient;
     41 import android.graphics.Rect;
     42 import android.graphics.RectF;
     43 import android.graphics.Shader;
     44 import android.graphics.SweepGradient;
     45 import android.graphics.Xfermode;
     46 import android.util.AttributeSet;
     47 import android.util.DisplayMetrics;
     48 import android.util.Log;
     49 import android.util.TypedValue;
     50 
     51 import com.android.internal.R;
     52 
     53 import org.xmlpull.v1.XmlPullParser;
     54 import org.xmlpull.v1.XmlPullParserException;
     55 
     56 import java.io.IOException;
     57 import java.lang.annotation.Retention;
     58 import 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  */
     95 public 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