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