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