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