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