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