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