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