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