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><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