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