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