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