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