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