OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / frameworks / base / graphics / java / android / graphics / drawable / GradientDrawable.java
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
17 package android.graphics.drawable;
18
19 import android.content.res.Resources;
20 import android.content.res.TypedArray;
21 import android.graphics.Canvas;
22 import android.graphics.ColorFilter;
23 import android.graphics.DashPathEffect;
24 import android.graphics.LinearGradient;
25 import android.graphics.Paint;
26 import android.graphics.PixelFormat;
27 import android.graphics.Rect;
28 import android.graphics.RectF;
29 import android.graphics.Shader;
30 import android.graphics.Path;
31 import android.graphics.RadialGradient;
32 import android.graphics.SweepGradient;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.util.TypedValue;
36
37 import org.xmlpull.v1.XmlPullParser;
38 import org.xmlpull.v1.XmlPullParserException;
39
40 import 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  */
77 public 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,
317                                        Path.Direction.CW);
318                     canvas.drawPath(mPath, mFillPaint);
319                     if (haveStroke) {
320                         canvas.drawPath(mPath, mStrokePaint);
321                     }
322                 }
323                 else {
324                     // since the caller is only giving us 1 value, we will force
325                     // it to be square if the rect is too small in one dimension
326                     // to show it. If we did nothing, Skia would clamp the rad
327                     // independently along each axis, giving us a thin ellips
328                     // if the rect were very wide but not very tall
329                     float rad = st.mRadius;
330                     float r = Math.min(mRect.width(), mRect.height()) * 0.5f;
331                     if (rad > r) {
332                         rad = r;
333                     }
334                     canvas.drawRoundRect(mRect, rad, rad, mFillPaint);
335                     if (haveStroke) {
336                         canvas.drawRoundRect(mRect, rad, rad, mStrokePaint);
337                     }
338                 }
339                 break;
340             case OVAL:
341                 canvas.drawOval(mRect, mFillPaint);
342                 if (haveStroke) {
343                     canvas.drawOval(mRect, mStrokePaint);
344                 }
345                 break;
346             case LINE: {
347                 RectF r = mRect;
348                 float y = r.centerY();
349                 canvas.drawLine(r.left, y, r.right, y, mStrokePaint);
350                 break;
351             }
352             case RING:
353                 Path path = buildRing(st);
354                 canvas.drawPath(path, mFillPaint);
355                 if (haveStroke) {
356                     canvas.drawPath(path, mStrokePaint);
357                 }
358                 break;
359         }
360         
361         if (useLayer) {
362             canvas.restore();
363         } else {
364             mFillPaint.setAlpha(prevFillAlpha);
365             if (haveStroke) {
366                 mStrokePaint.setAlpha(prevStrokeAlpha);
367             }
368         }
369     }
370     
371     private Path buildRing(GradientState st) {
372         if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath;
373         mPathIsDirty = false;
374
375         float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f;
376         
377         RectF bounds = new RectF(mRect);
378
379         float x = bounds.width() / 2.0f;
380         float y = bounds.height() / 2.0f;
381
382         float thickness = st.mThickness != -1 ?
383                 st.mThickness : bounds.width() / st.mThicknessRatio;
384         // inner radius
385         float radius = st.mInnerRadius != -1 ?
386                 st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio;
387
388         RectF innerBounds = new RectF(bounds);
389         innerBounds.inset(x - radius, y - radius);
390
391         bounds = new RectF(innerBounds);
392         bounds.inset(-thickness, -thickness);
393
394         if (mRingPath == null) {
395             mRingPath = new Path();
396         } else {
397             mRingPath.reset();            
398         }
399
400         final Path ringPath = mRingPath;
401         // arcTo treats the sweep angle mod 360, so check for that, since we
402         // think 360 means draw the entire oval
403         if (sweep < 360 && sweep > -360) {
404             ringPath.setFillType(Path.FillType.EVEN_ODD);
405             // inner top
406             ringPath.moveTo(x + radius, y);
407             // outer top
408             ringPath.lineTo(x + radius + thickness, y);
409             // outer arc
410             ringPath.arcTo(bounds, 0.0f, sweep, false);
411             // inner arc
412             ringPath.arcTo(innerBounds, sweep, -sweep, false);
413             ringPath.close();
414         } else {
415             // add the entire ovals
416             ringPath.addOval(bounds, Path.Direction.CW);
417             ringPath.addOval(innerBounds, Path.Direction.CCW);
418         }
419
420         return ringPath;
421     }
422
423     public void setColor(int argb) {
424         mGradientState.setSolidColor(argb);
425         mFillPaint.setColor(argb);
426     }
427
428     @Override
429     public int getChangingConfigurations() {
430         return super.getChangingConfigurations()
431                 | mGradientState.mChangingConfigurations;
432     }
433     
434     @Override
435     public void setAlpha(int alpha) {
436         mAlpha = alpha;
437     }
438
439     @Override
440     public void setDither(boolean dither) {
441         mDither = dither;
442     }
443
444     @Override
445     public void setColorFilter(ColorFilter cf) {
446         mColorFilter = cf;
447     }
448
449     @Override
450     public int getOpacity() {
451         // XXX need to figure out the actual opacity...
452         return PixelFormat.TRANSLUCENT;
453     }
454
455     @Override
456     protected void onBoundsChange(Rect r) {
457         super.onBoundsChange(r);
458         mRingPath = null;
459         mPathIsDirty = true;
460         mRectIsDirty = true;
461     }
462
463     @Override
464     protected boolean onLevelChange(int level) {
465         super.onLevelChange(level);
466         mRectIsDirty = true;
467         mPathIsDirty = true;
468         invalidateSelf();
469         return true;
470     }
471
472     /**
473      * This checks mRectIsDirty, and if it is true, recomputes both our drawing
474      * rectangle (mRect) and the gradient itself, since it depends on our
475      * rectangle too.
476      * @return true if the resulting rectangle is not empty, false otherwise
477      */
478     private boolean ensureValidRect() {
479         if (mRectIsDirty) {
480             mRectIsDirty = false;
481
482             Rect bounds = getBounds();
483             float inset = 0;
484             
485             if (mStrokePaint != null) {
486                 inset = mStrokePaint.getStrokeWidth() * 0.5f;
487             }
488
489             final GradientState st = mGradientState;
490
491             mRect.set(bounds.left + inset, bounds.top + inset,
492                       bounds.right - inset, bounds.bottom - inset);
493
494             final int[] colors = st.mColors;
495             if (colors != null) {
496                 RectF r = mRect;
497                 float x0, x1, y0, y1;
498
499                 if (st.mGradient == LINEAR_GRADIENT) {
500                     final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f;                    
501                     switch (st.mOrientation) {
502                     case TOP_BOTTOM:
503                         x0 = r.left;            y0 = r.top;
504                         x1 = x0;                y1 = level * r.bottom;
505                         break;
506                     case TR_BL:
507                         x0 = r.right;           y0 = r.top;
508                         x1 = level * r.left;    y1 = level * r.bottom;
509                         break;
510                     case RIGHT_LEFT:
511                         x0 = r.right;           y0 = r.top;
512                         x1 = level * r.left;    y1 = y0;
513                         break;
514                     case BR_TL:
515                         x0 = r.right;           y0 = r.bottom;
516                         x1 = level * r.left;    y1 = level * r.top;
517                         break;
518                     case BOTTOM_TOP:
519                         x0 = r.left;            y0 = r.bottom;
520                         x1 = x0;                y1 = level * r.top;
521                         break;
522                     case BL_TR:
523                         x0 = r.left;            y0 = r.bottom;
524                         x1 = level * r.right;   y1 = level * r.top;
525                         break;
526                     case LEFT_RIGHT:
527                         x0 = r.left;            y0 = r.top;
528                         x1 = level * r.right;   y1 = y0;
529                         break;
530                     default:/* TL_BR */
531                         x0 = r.left;            y0 = r.top;
532                         x1 = level * r.right;   y1 = level * r.bottom;
533                         break;
534                     }
535
536                     mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1,
537                             colors, st.mPositions, Shader.TileMode.CLAMP));
538                 } else if (st.mGradient == RADIAL_GRADIENT) {
539                     x0 = r.left + (r.right - r.left) * st.mCenterX;
540                     y0 = r.top + (r.bottom - r.top) * st.mCenterY;
541
542                     final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f;
543
544                     mFillPaint.setShader(new RadialGradient(x0, y0,
545                             level * st.mGradientRadius, colors, null,
546                             Shader.TileMode.CLAMP));
547                 } else if (st.mGradient == SWEEP_GRADIENT) {
548                     x0 = r.left + (r.right - r.left) * st.mCenterX;
549                     y0 = r.top + (r.bottom - r.top) * st.mCenterY;
550
551                     int[] tempColors = colors;
552                     float[] tempPositions = null;
553
554                     if (st.mUseLevel) {
555                         tempColors = st.mTempColors;
556                         final int length = colors.length;
557                         if (tempColors == null || tempColors.length != length + 1) {
558                             tempColors = st.mTempColors = new int[length + 1];
559                         }
560                         System.arraycopy(colors, 0, tempColors, 0, length);
561                         tempColors[length] = colors[length - 1];
562
563                         tempPositions = st.mTempPositions;
564                         final float fraction = 1.0f / (float) (length - 1);
565                         if (tempPositions == null || tempPositions.length != length + 1) {
566                             tempPositions = st.mTempPositions = new float[length + 1];
567                         }
568
569                         final float level = (float) getLevel() / 10000.0f;
570                         for (int i = 0; i < length; i++) {
571                             tempPositions[i] = i * fraction * level;
572                         }
573                         tempPositions[length] = 1.0f;
574
575                     }
576                     mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions));
577                 }
578             }
579         }
580         return !mRect.isEmpty();
581     }
582
583     @Override
584     public void inflate(Resources r, XmlPullParser parser,
585             AttributeSet attrs)
586             throws XmlPullParserException, IOException {
587         
588         final GradientState st = mGradientState;
589         
590         TypedArray a = r.obtainAttributes(attrs,
591                 com.android.internal.R.styleable.GradientDrawable);
592
593         super.inflateWithAttributes(r, parser, a,
594                 com.android.internal.R.styleable.GradientDrawable_visible);
595         
596         int shapeType = a.getInt(
597                 com.android.internal.R.styleable.GradientDrawable_shape, RECTANGLE);
598         
599         if (shapeType == RING) {
600             st.mInnerRadius = a.getDimensionPixelSize(
601                     com.android.internal.R.styleable.GradientDrawable_innerRadius, -1);
602             if (st.mInnerRadius == -1) {
603                 st.mInnerRadiusRatio = a.getFloat(
604                         com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f);
605             }
606             st.mThickness = a.getDimensionPixelSize(
607                     com.android.internal.R.styleable.GradientDrawable_thickness, -1);
608             if (st.mThickness == -1) {
609                 st.mThicknessRatio = a.getFloat(
610                         com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f);
611             }
612             st.mUseLevelForShape = a.getBoolean(
613                     com.android.internal.R.styleable.GradientDrawable_useLevel, true);
614         }
615         
616         a.recycle();
617         
618         setShape(shapeType);
619         
620         int type;
621
622         final int innerDepth = parser.getDepth()+1;
623         int depth;
624         while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
625                && ((depth=parser.getDepth()) >= innerDepth
626                        || type != XmlPullParser.END_TAG)) {
627             if (type != XmlPullParser.START_TAG) {
628                 continue;
629             }
630
631             if (depth > innerDepth) {
632                 continue;
633             }
634             
635             String name = parser.getName();
636             
637             if (name.equals("size")) {
638                 a = r.obtainAttributes(attrs,
639                         com.android.internal.R.styleable.GradientDrawableSize);
640                 int width = a.getDimensionPixelSize(
641                         com.android.internal.R.styleable.GradientDrawableSize_width, -1);
642                 int height = a.getDimensionPixelSize(
643                         com.android.internal.R.styleable.GradientDrawableSize_height, -1);
644                 a.recycle();
645                 setSize(width, height);
646             } else if (name.equals("gradient")) {
647                 a = r.obtainAttributes(attrs,
648                         com.android.internal.R.styleable.GradientDrawableGradient);
649                 int startColor = a.getColor(
650                         com.android.internal.R.styleable.GradientDrawableGradient_startColor, 0);
651                 boolean hasCenterColor = a
652                         .hasValue(com.android.internal.R.styleable.GradientDrawableGradient_centerColor);
653                 int centerColor = a.getColor(
654                         com.android.internal.R.styleable.GradientDrawableGradient_centerColor, 0);
655                 int endColor = a.getColor(
656                         com.android.internal.R.styleable.GradientDrawableGradient_endColor, 0);
657                 int gradientType = a.getInt(
658                         com.android.internal.R.styleable.GradientDrawableGradient_type,
659                         LINEAR_GRADIENT);
660
661                 st.mCenterX = getFloatOrFraction(
662                         a,
663                         com.android.internal.R.styleable.GradientDrawableGradient_centerX,
664                         0.5f);
665
666                 st.mCenterY = getFloatOrFraction(
667                         a,
668                         com.android.internal.R.styleable.GradientDrawableGradient_centerY,
669                         0.5f);
670
671                 st.mUseLevel = a.getBoolean(
672                         com.android.internal.R.styleable.GradientDrawableGradient_useLevel, false);
673                 st.mGradient = gradientType;
674
675                 if (gradientType == LINEAR_GRADIENT) {
676                     int angle = (int)a.getFloat(
677                             com.android.internal.R.styleable.GradientDrawableGradient_angle, 0);
678                     angle %= 360;
679                     if (angle % 45 != 0) {
680                         throw new XmlPullParserException(a.getPositionDescription()
681                                 + "<gradient> tag requires 'angle' attribute to "
682                                 + "be a multiple of 45");
683                     }
684
685                     switch (angle) {
686                     case 0:
687                         st.mOrientation = Orientation.LEFT_RIGHT;
688                         break;
689                     case 45:
690                         st.mOrientation = Orientation.BL_TR;
691                         break;
692                     case 90:
693                         st.mOrientation = Orientation.BOTTOM_TOP;
694                         break;
695                     case 135:
696                         st.mOrientation = Orientation.BR_TL;
697                         break;
698                     case 180:
699                         st.mOrientation = Orientation.RIGHT_LEFT;
700                         break;
701                     case 225:
702                         st.mOrientation = Orientation.TR_BL;
703                         break;
704                     case 270:
705                         st.mOrientation = Orientation.TOP_BOTTOM;
706                         break;
707                     case 315:
708                         st.mOrientation = Orientation.TL_BR;
709                         break;
710                     }
711                 } else {
712                     TypedValue tv = a.peekValue(
713                             com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius);
714                     if (tv != null) {
715                         boolean radiusRel = tv.type == TypedValue.TYPE_FRACTION;
716                         st.mGradientRadius = radiusRel ?
717                                 tv.getFraction(1.0f, 1.0f) : tv.getFloat();
718                     } else if (gradientType == RADIAL_GRADIENT) {
719                         throw new XmlPullParserException(
720                                 a.getPositionDescription()
721                                 + "<gradient> tag requires 'gradientRadius' "
722                                 + "attribute with radial type");
723                     }
724                 }
725
726                 a.recycle();
727
728                 if (hasCenterColor) {
729                     st.mColors = new int[3];
730                     st.mColors[0] = startColor;
731                     st.mColors[1] = centerColor;
732                     st.mColors[2] = endColor;
733                     
734                     st.mPositions = new float[3];
735                     st.mPositions[0] = 0.0f;
736                     // Since 0.5f is default value, try to take the one that isn't 0.5f
737                     st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY;
738                     st.mPositions[2] = 1f;
739                 } else {
740                     st.mColors = new int[2];
741                     st.mColors[0] = startColor;
742                     st.mColors[1] = endColor;
743                 }
744                 
745             } else if (name.equals("solid")) {
746                 a = r.obtainAttributes(attrs,
747                         com.android.internal.R.styleable.GradientDrawableSolid);
748                 int argb = a.getColor(
749                         com.android.internal.R.styleable.GradientDrawableSolid_color, 0);
750                 a.recycle();
751                 setColor(argb);
752             } else if (name.equals("stroke")) {
753                 a = r.obtainAttributes(attrs,
754                         com.android.internal.R.styleable.GradientDrawableStroke);
755                 int width = a.getDimensionPixelSize(
756                         com.android.internal.R.styleable.GradientDrawableStroke_width, 0);
757                 int color = a.getColor(
758                         com.android.internal.R.styleable.GradientDrawableStroke_color, 0);
759                 float dashWidth = a.getDimension(
760                         com.android.internal.R.styleable.GradientDrawableStroke_dashWidth, 0);
761                 if (dashWidth != 0.0f) {
762                     float dashGap = a.getDimension(
763                             com.android.internal.R.styleable.GradientDrawableStroke_dashGap, 0);
764                     setStroke(width, color, dashWidth, dashGap);
765                 } else {
766                     setStroke(width, color);
767                 }
768                 a.recycle();
769             } else if (name.equals("corners")) {
770                 a = r.obtainAttributes(attrs,
771                         com.android.internal.R.styleable.DrawableCorners);
772                 int radius = a.getDimensionPixelSize(
773                         com.android.internal.R.styleable.DrawableCorners_radius, 0);
774                 setCornerRadius(radius);
775                 int topLeftRadius = a.getDimensionPixelSize(
776                         com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius);
777                 int topRightRadius = a.getDimensionPixelSize(
778                         com.android.internal.R.styleable.DrawableCorners_topRightRadius, radius);
779                 int bottomLeftRadius = a.getDimensionPixelSize(
780                         com.android.internal.R.styleable.DrawableCorners_bottomLeftRadius, radius);
781                 int bottomRightRadius = a.getDimensionPixelSize(
782                         com.android.internal.R.styleable.DrawableCorners_bottomRightRadius, radius);
783                 if (topLeftRadius != radius || topRightRadius != radius ||
784                         bottomLeftRadius != radius || bottomRightRadius != radius) {
785                     setCornerRadii(new float[] {
786                             topLeftRadius, topLeftRadius,
787                             topRightRadius, topRightRadius,
788                             bottomLeftRadius, bottomLeftRadius,
789                             bottomRightRadius, bottomRightRadius
790                     });
791                 }
792                 a.recycle();
793             } else if (name.equals("padding")) {
794                 a = r.obtainAttributes(attrs,
795                         com.android.internal.R.styleable.GradientDrawablePadding);
796                 mPadding = new Rect(
797                         a.getDimensionPixelOffset(
798                                 com.android.internal.R.styleable.GradientDrawablePadding_left, 0),
799                         a.getDimensionPixelOffset(
800                                 com.android.internal.R.styleable.GradientDrawablePadding_top, 0),
801                         a.getDimensionPixelOffset(
802                                 com.android.internal.R.styleable.GradientDrawablePadding_right, 0),
803                         a.getDimensionPixelOffset(
804                                 com.android.internal.R.styleable.GradientDrawablePadding_bottom, 0));
805                 a.recycle();
806                 mGradientState.mPadding = mPadding;
807             } else {
808                 Log.w("drawable", "Bad element under <shape>: " + name);
809             }
810         }
811     }
812
813     private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) {
814         TypedValue tv = a.peekValue(index);
815         float v = defaultValue;
816         if (tv != null) {
817             boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION;
818             v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
819         }
820         return v;
821     }
822     
823     @Override
824     public int getIntrinsicWidth() {
825         return mGradientState.mWidth;
826     }
827
828     @Override
829     public int getIntrinsicHeight() {
830         return mGradientState.mHeight;
831     }
832     
833     @Override
834     public ConstantState getConstantState() {
835         mGradientState.mChangingConfigurations = super.getChangingConfigurations();
836         return mGradientState;
837     }
838
839     @Override
840     public Drawable mutate() {
841         if (!mMutated && super.mutate() == this) {
842             mGradientState = new GradientState(mGradientState);
843             initializeWithState(mGradientState);
844             mMutated = true;
845         }
846         return this;
847     }
848
849     final static class GradientState extends ConstantState {
850         public int mChangingConfigurations;
851         public int mShape = RECTANGLE;
852         public int mGradient = LINEAR_GRADIENT;
853         public Orientation mOrientation;
854         public int[] mColors;
855         public int[] mTempColors; // no need to copy
856         public float[] mTempPositions; // no need to copy
857         public float[] mPositions;
858         public boolean mHasSolidColor;
859         public int mSolidColor;
860         public int mStrokeWidth = -1;   // if >= 0 use stroking.
861         public int mStrokeColor;
862         public float mStrokeDashWidth;
863         public float mStrokeDashGap;
864         public float mRadius;    // use this if mRadiusArray is null
865         public float[] mRadiusArray;
866         public Rect mPadding;
867         public int mWidth = -1;
868         public int mHeight = -1;
869         public float mInnerRadiusRatio;
870         public float mThicknessRatio;
871         public int mInnerRadius;
872         public int mThickness;
873         private float mCenterX = 0.5f;
874         private float mCenterY = 0.5f;
875         private float mGradientRadius = 0.5f;
876         private boolean mUseLevel;
877         private boolean mUseLevelForShape;
878         
879         
880         GradientState() {
881             mOrientation = Orientation.TOP_BOTTOM;
882         }
883
884         GradientState(Orientation orientation, int[] colors) {
885             mOrientation = orientation;
886             mColors = colors;
887         }
888
889         public GradientState(GradientState state) {
890             mChangingConfigurations = state.mChangingConfigurations;
891             mShape = state.mShape;
892             mGradient = state.mGradient;
893             mOrientation = state.mOrientation;
894             if (state.mColors != null) {
895                 mColors = state.mColors.clone();
896             }
897             if (state.mPositions != null) {
898                 mPositions = state.mPositions.clone();
899             }
900             mHasSolidColor = state.mHasSolidColor;
901             mStrokeWidth = state.mStrokeWidth;
902             mStrokeColor = state.mStrokeColor;
903             mStrokeDashWidth = state.mStrokeDashWidth;
904             mStrokeDashGap = state.mStrokeDashGap;
905             mRadius = state.mRadius;
906             if (state.mRadiusArray != null) {
907                 mRadiusArray = state.mRadiusArray.clone();
908             }
909             if (state.mPadding != null) {
910                 mPadding = new Rect(state.mPadding);
911             }
912             mWidth = state.mWidth;
913             mHeight = state.mHeight;
914             mInnerRadiusRatio = state.mInnerRadiusRatio;
915             mThicknessRatio = state.mThicknessRatio;
916             mInnerRadius = state.mInnerRadius;
917             mThickness = state.mThickness;
918             mCenterX = state.mCenterX;
919             mCenterY = state.mCenterY;
920             mGradientRadius = state.mGradientRadius;
921             mUseLevel = state.mUseLevel;
922             mUseLevelForShape = state.mUseLevelForShape;
923         }
924
925         @Override
926         public Drawable newDrawable() {
927             return new GradientDrawable(this);
928         }
929         
930         @Override
931         public Drawable newDrawable(Resources res) {
932             return new GradientDrawable(this);
933         }
934         
935         @Override
936         public int getChangingConfigurations() {
937             return mChangingConfigurations;
938         }
939
940         public void setShape(int shape) {
941             mShape = shape;
942         }
943
944         public void setGradientType(int gradient) {
945             mGradient = gradient;
946         }
947
948         public void setGradientCenter(float x, float y) {
949             mCenterX = x;
950             mCenterY = y;
951         }
952
953         public void setSolidColor(int argb) {
954             mHasSolidColor = true;
955             mSolidColor = argb;
956             mColors = null;
957         }
958
959         public void setStroke(int width, int color) {
960             mStrokeWidth = width;
961             mStrokeColor = color;
962         }
963         
964         public void setStroke(int width, int color, float dashWidth, float dashGap) {
965             mStrokeWidth = width;
966             mStrokeColor = color;
967             mStrokeDashWidth = dashWidth;
968             mStrokeDashGap = dashGap;
969         }
970
971         public void setCornerRadius(float radius) {
972             if (radius < 0) {
973                 radius = 0;
974             }
975             mRadius = radius;
976             mRadiusArray = null;
977         }
978         
979         public void setCornerRadii(float[] radii) {
980             mRadiusArray = radii;
981             if (radii == null) {
982                 mRadius = 0;
983             }
984         }
985         
986         public void setSize(int width, int height) {
987             mWidth = width;
988             mHeight = height;
989         }
990
991         public void setGradientRadius(float gradientRadius) {
992             mGradientRadius = gradientRadius;
993         }
994     }
995
996     private GradientDrawable(GradientState state) {
997         mGradientState = state;
998         initializeWithState(state);
999         mRectIsDirty = true;
1000     }
1001
1002     private void initializeWithState(GradientState state) {
1003         if (state.mHasSolidColor) {
1004             mFillPaint.setColor(state.mSolidColor);
1005         }
1006         mPadding = state.mPadding;
1007         if (state.mStrokeWidth >= 0) {
1008             mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
1009             mStrokePaint.setStyle(Paint.Style.STROKE);
1010             mStrokePaint.setStrokeWidth(state.mStrokeWidth);
1011             mStrokePaint.setColor(state.mStrokeColor);
1012
1013             if (state.mStrokeDashWidth != 0.0f) {
1014                 DashPathEffect e = new DashPathEffect(
1015                         new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
1016                 mStrokePaint.setPathEffect(e);
1017             }
1018         }
1019     }
1020 }
1021