1/* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.graphics.drawable; 18 19import android.content.res.Resources; 20import android.content.res.TypedArray; 21import android.graphics.Canvas; 22import android.graphics.ColorFilter; 23import android.graphics.DashPathEffect; 24import android.graphics.LinearGradient; 25import android.graphics.Paint; 26import android.graphics.PixelFormat; 27import android.graphics.Rect; 28import android.graphics.RectF; 29import android.graphics.Shader; 30import android.graphics.Path; 31import android.graphics.RadialGradient; 32import android.graphics.SweepGradient; 33import android.util.AttributeSet; 34import android.util.Log; 35import android.util.TypedValue; 36 37import org.xmlpull.v1.XmlPullParser; 38import org.xmlpull.v1.XmlPullParserException; 39 40import java.io.IOException; 41 42/** 43 * A Drawable with a color gradient for buttons, backgrounds, etc. 44 * 45 * <p>It can be defined in an XML file with the <code><shape></code> element. For more 46 * information, see the guide to <a 47 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 48 * 49 * @attr ref android.R.styleable#GradientDrawable_visible 50 * @attr ref android.R.styleable#GradientDrawable_shape 51 * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio 52 * @attr ref android.R.styleable#GradientDrawable_innerRadius 53 * @attr ref android.R.styleable#GradientDrawable_thicknessRatio 54 * @attr ref android.R.styleable#GradientDrawable_thickness 55 * @attr ref android.R.styleable#GradientDrawable_useLevel 56 * @attr ref android.R.styleable#GradientDrawableSize_width 57 * @attr ref android.R.styleable#GradientDrawableSize_height 58 * @attr ref android.R.styleable#GradientDrawableGradient_startColor 59 * @attr ref android.R.styleable#GradientDrawableGradient_centerColor 60 * @attr ref android.R.styleable#GradientDrawableGradient_endColor 61 * @attr ref android.R.styleable#GradientDrawableGradient_useLevel 62 * @attr ref android.R.styleable#GradientDrawableGradient_angle 63 * @attr ref android.R.styleable#GradientDrawableGradient_type 64 * @attr ref android.R.styleable#GradientDrawableGradient_centerX 65 * @attr ref android.R.styleable#GradientDrawableGradient_centerY 66 * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius 67 * @attr ref android.R.styleable#GradientDrawableSolid_color 68 * @attr ref android.R.styleable#GradientDrawableStroke_width 69 * @attr ref android.R.styleable#GradientDrawableStroke_color 70 * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth 71 * @attr ref android.R.styleable#GradientDrawableStroke_dashGap 72 * @attr ref android.R.styleable#GradientDrawablePadding_left 73 * @attr ref android.R.styleable#GradientDrawablePadding_top 74 * @attr ref android.R.styleable#GradientDrawablePadding_right 75 * @attr ref android.R.styleable#GradientDrawablePadding_bottom 76 */ 77public class GradientDrawable extends Drawable { 78 /** 79 * Shape is a rectangle, possibly with rounded corners 80 */ 81 public static final int RECTANGLE = 0; 82 83 /** 84 * Shape is an ellipse 85 */ 86 public static final int OVAL = 1; 87 88 /** 89 * Shape is a line 90 */ 91 public static final int LINE = 2; 92 93 /** 94 * Shape is a ring. 95 */ 96 public static final int RING = 3; 97 98 /** 99 * Gradient is linear (default.) 100 */ 101 public static final int LINEAR_GRADIENT = 0; 102 103 /** 104 * Gradient is circular. 105 */ 106 public static final int RADIAL_GRADIENT = 1; 107 108 /** 109 * Gradient is a sweep. 110 */ 111 public static final int SWEEP_GRADIENT = 2; 112 113 private GradientState mGradientState; 114 115 private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 116 private Rect mPadding; 117 private Paint mStrokePaint; // optional, set by the caller 118 private ColorFilter mColorFilter; // optional, set by the caller 119 private int mAlpha = 0xFF; // modified by the caller 120 private boolean mDither; 121 122 private final Path mPath = new Path(); 123 private final RectF mRect = new RectF(); 124 125 private Paint mLayerPaint; // internal, used if we use saveLayer() 126 private boolean mRectIsDirty; // internal state 127 private boolean mMutated; 128 private Path mRingPath; 129 private boolean mPathIsDirty = 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 != null && 439 mStrokePaint.getStrokeWidth() > 0; 440 final boolean haveFill = currFillAlpha > 0; 441 final GradientState st = mGradientState; 442 /* we need a layer iff we're drawing both a fill and stroke, and the 443 stroke is non-opaque, and our shapetype actually supports 444 fill+stroke. Otherwise we can just draw the stroke (if any) on top 445 of the fill (if any) without worrying about blending artifacts. 446 */ 447 final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && 448 currStrokeAlpha < 255 && (mAlpha < 255 || mColorFilter != null); 449 450 /* Drawing with a layer is slower than direct drawing, but it 451 allows us to apply paint effects like alpha and colorfilter to 452 the result of multiple separate draws. In our case, if the user 453 asks for a non-opaque alpha value (via setAlpha), and we're 454 stroking, then we need to apply the alpha AFTER we've drawn 455 both the fill and the stroke. 456 */ 457 if (useLayer) { 458 if (mLayerPaint == null) { 459 mLayerPaint = new Paint(); 460 } 461 mLayerPaint.setDither(mDither); 462 mLayerPaint.setAlpha(mAlpha); 463 mLayerPaint.setColorFilter(mColorFilter); 464 465 float rad = mStrokePaint.getStrokeWidth(); 466 canvas.saveLayer(mRect.left - rad, mRect.top - rad, 467 mRect.right + rad, mRect.bottom + rad, 468 mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); 469 470 // don't perform the filter in our individual paints 471 // since the layer will do it for us 472 mFillPaint.setColorFilter(null); 473 mStrokePaint.setColorFilter(null); 474 } else { 475 /* if we're not using a layer, apply the dither/filter to our 476 individual paints 477 */ 478 mFillPaint.setAlpha(currFillAlpha); 479 mFillPaint.setDither(mDither); 480 mFillPaint.setColorFilter(mColorFilter); 481 if (mColorFilter != null && !mGradientState.mHasSolidColor) { 482 mFillPaint.setColor(mAlpha << 24); 483 } 484 if (haveStroke) { 485 mStrokePaint.setAlpha(currStrokeAlpha); 486 mStrokePaint.setDither(mDither); 487 mStrokePaint.setColorFilter(mColorFilter); 488 } 489 } 490 491 switch (st.mShape) { 492 case RECTANGLE: 493 if (st.mRadiusArray != null) { 494 if (mPathIsDirty || mRectIsDirty) { 495 mPath.reset(); 496 mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); 497 mPathIsDirty = mRectIsDirty = false; 498 } 499 canvas.drawPath(mPath, mFillPaint); 500 if (haveStroke) { 501 canvas.drawPath(mPath, mStrokePaint); 502 } 503 } else if (st.mRadius > 0.0f) { 504 // since the caller is only giving us 1 value, we will force 505 // it to be square if the rect is too small in one dimension 506 // to show it. If we did nothing, Skia would clamp the rad 507 // independently along each axis, giving us a thin ellipse 508 // if the rect were very wide but not very tall 509 float rad = st.mRadius; 510 float r = Math.min(mRect.width(), mRect.height()) * 0.5f; 511 if (rad > r) { 512 rad = r; 513 } 514 canvas.drawRoundRect(mRect, rad, rad, mFillPaint); 515 if (haveStroke) { 516 canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); 517 } 518 } else { 519 if (mFillPaint.getColor() != 0 || mColorFilter != null || 520 mFillPaint.getShader() != null) { 521 canvas.drawRect(mRect, mFillPaint); 522 } 523 if (haveStroke) { 524 canvas.drawRect(mRect, mStrokePaint); 525 } 526 } 527 break; 528 case OVAL: 529 canvas.drawOval(mRect, mFillPaint); 530 if (haveStroke) { 531 canvas.drawOval(mRect, mStrokePaint); 532 } 533 break; 534 case LINE: { 535 RectF r = mRect; 536 float y = r.centerY(); 537 canvas.drawLine(r.left, y, r.right, y, mStrokePaint); 538 break; 539 } 540 case RING: 541 Path path = buildRing(st); 542 canvas.drawPath(path, mFillPaint); 543 if (haveStroke) { 544 canvas.drawPath(path, mStrokePaint); 545 } 546 break; 547 } 548 549 if (useLayer) { 550 canvas.restore(); 551 } else { 552 mFillPaint.setAlpha(prevFillAlpha); 553 if (haveStroke) { 554 mStrokePaint.setAlpha(prevStrokeAlpha); 555 } 556 } 557 } 558 559 private Path buildRing(GradientState st) { 560 if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath; 561 mPathIsDirty = false; 562 563 float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f; 564 565 RectF bounds = new RectF(mRect); 566 567 float x = bounds.width() / 2.0f; 568 float y = bounds.height() / 2.0f; 569 570 float thickness = st.mThickness != -1 ? 571 st.mThickness : bounds.width() / st.mThicknessRatio; 572 // inner radius 573 float radius = st.mInnerRadius != -1 ? 574 st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio; 575 576 RectF innerBounds = new RectF(bounds); 577 innerBounds.inset(x - radius, y - radius); 578 579 bounds = new RectF(innerBounds); 580 bounds.inset(-thickness, -thickness); 581 582 if (mRingPath == null) { 583 mRingPath = new Path(); 584 } else { 585 mRingPath.reset(); 586 } 587 588 final Path ringPath = mRingPath; 589 // arcTo treats the sweep angle mod 360, so check for that, since we 590 // think 360 means draw the entire oval 591 if (sweep < 360 && sweep > -360) { 592 ringPath.setFillType(Path.FillType.EVEN_ODD); 593 // inner top 594 ringPath.moveTo(x + radius, y); 595 // outer top 596 ringPath.lineTo(x + radius + thickness, y); 597 // outer arc 598 ringPath.arcTo(bounds, 0.0f, sweep, false); 599 // inner arc 600 ringPath.arcTo(innerBounds, sweep, -sweep, false); 601 ringPath.close(); 602 } else { 603 // add the entire ovals 604 ringPath.addOval(bounds, Path.Direction.CW); 605 ringPath.addOval(innerBounds, Path.Direction.CCW); 606 } 607 608 return ringPath; 609 } 610 611 /** 612 * <p>Changes this drawbale to use a single color instead of a gradient.</p> 613 * <p><strong>Note</strong>: changing color will affect all instances 614 * of a drawable loaded from a resource. It is recommended to invoke 615 * {@link #mutate()} before changing the color.</p> 616 * 617 * @param argb The color used to fill the shape 618 * 619 * @see #mutate() 620 * @see #setColors(int[]) 621 */ 622 public void setColor(int argb) { 623 mGradientState.setSolidColor(argb); 624 mFillPaint.setColor(argb); 625 invalidateSelf(); 626 } 627 628 @Override 629 public int getChangingConfigurations() { 630 return super.getChangingConfigurations() | mGradientState.mChangingConfigurations; 631 } 632 633 @Override 634 public void setAlpha(int alpha) { 635 if (alpha != mAlpha) { 636 mAlpha = alpha; 637 invalidateSelf(); 638 } 639 } 640 641 @Override 642 public void setDither(boolean dither) { 643 if (dither != mDither) { 644 mDither = dither; 645 invalidateSelf(); 646 } 647 } 648 649 @Override 650 public void setColorFilter(ColorFilter cf) { 651 if (cf != mColorFilter) { 652 mColorFilter = cf; 653 invalidateSelf(); 654 } 655 } 656 657 @Override 658 public int getOpacity() { 659 return mGradientState.mOpaque ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; 660 } 661 662 @Override 663 protected void onBoundsChange(Rect r) { 664 super.onBoundsChange(r); 665 mRingPath = null; 666 mPathIsDirty = true; 667 mRectIsDirty = true; 668 } 669 670 @Override 671 protected boolean onLevelChange(int level) { 672 super.onLevelChange(level); 673 mRectIsDirty = true; 674 mPathIsDirty = true; 675 invalidateSelf(); 676 return true; 677 } 678 679 /** 680 * This checks mRectIsDirty, and if it is true, recomputes both our drawing 681 * rectangle (mRect) and the gradient itself, since it depends on our 682 * rectangle too. 683 * @return true if the resulting rectangle is not empty, false otherwise 684 */ 685 private boolean ensureValidRect() { 686 if (mRectIsDirty) { 687 mRectIsDirty = false; 688 689 Rect bounds = getBounds(); 690 float inset = 0; 691 692 if (mStrokePaint != null) { 693 inset = mStrokePaint.getStrokeWidth() * 0.5f; 694 } 695 696 final GradientState st = mGradientState; 697 698 mRect.set(bounds.left + inset, bounds.top + inset, 699 bounds.right - inset, bounds.bottom - inset); 700 701 final int[] colors = st.mColors; 702 if (colors != null) { 703 RectF r = mRect; 704 float x0, x1, y0, y1; 705 706 if (st.mGradient == LINEAR_GRADIENT) { 707 final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f; 708 switch (st.mOrientation) { 709 case TOP_BOTTOM: 710 x0 = r.left; y0 = r.top; 711 x1 = x0; y1 = level * r.bottom; 712 break; 713 case TR_BL: 714 x0 = r.right; y0 = r.top; 715 x1 = level * r.left; y1 = level * r.bottom; 716 break; 717 case RIGHT_LEFT: 718 x0 = r.right; y0 = r.top; 719 x1 = level * r.left; y1 = y0; 720 break; 721 case BR_TL: 722 x0 = r.right; y0 = r.bottom; 723 x1 = level * r.left; y1 = level * r.top; 724 break; 725 case BOTTOM_TOP: 726 x0 = r.left; y0 = r.bottom; 727 x1 = x0; y1 = level * r.top; 728 break; 729 case BL_TR: 730 x0 = r.left; y0 = r.bottom; 731 x1 = level * r.right; y1 = level * r.top; 732 break; 733 case LEFT_RIGHT: 734 x0 = r.left; y0 = r.top; 735 x1 = level * r.right; y1 = y0; 736 break; 737 default:/* TL_BR */ 738 x0 = r.left; y0 = r.top; 739 x1 = level * r.right; y1 = level * r.bottom; 740 break; 741 } 742 743 mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, 744 colors, st.mPositions, Shader.TileMode.CLAMP)); 745 if (!mGradientState.mHasSolidColor) { 746 mFillPaint.setColor(mAlpha << 24); 747 } 748 } else if (st.mGradient == RADIAL_GRADIENT) { 749 x0 = r.left + (r.right - r.left) * st.mCenterX; 750 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 751 752 final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f; 753 754 mFillPaint.setShader(new RadialGradient(x0, y0, 755 level * st.mGradientRadius, colors, null, 756 Shader.TileMode.CLAMP)); 757 if (!mGradientState.mHasSolidColor) { 758 mFillPaint.setColor(mAlpha << 24); 759 } 760 } else if (st.mGradient == SWEEP_GRADIENT) { 761 x0 = r.left + (r.right - r.left) * st.mCenterX; 762 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 763 764 int[] tempColors = colors; 765 float[] tempPositions = null; 766 767 if (st.mUseLevel) { 768 tempColors = st.mTempColors; 769 final int length = colors.length; 770 if (tempColors == null || tempColors.length != length + 1) { 771 tempColors = st.mTempColors = new int[length + 1]; 772 } 773 System.arraycopy(colors, 0, tempColors, 0, length); 774 tempColors[length] = colors[length - 1]; 775 776 tempPositions = st.mTempPositions; 777 final float fraction = 1.0f / (float) (length - 1); 778 if (tempPositions == null || tempPositions.length != length + 1) { 779 tempPositions = st.mTempPositions = new float[length + 1]; 780 } 781 782 final float level = (float) getLevel() / 10000.0f; 783 for (int i = 0; i < length; i++) { 784 tempPositions[i] = i * fraction * level; 785 } 786 tempPositions[length] = 1.0f; 787 788 } 789 mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); 790 if (!mGradientState.mHasSolidColor) { 791 mFillPaint.setColor(mAlpha << 24); 792 } 793 } 794 } 795 } 796 return !mRect.isEmpty(); 797 } 798 799 @Override 800 public void inflate(Resources r, XmlPullParser parser, 801 AttributeSet attrs) 802 throws XmlPullParserException, IOException { 803 804 final GradientState st = mGradientState; 805 806 TypedArray a = r.obtainAttributes(attrs, 807 com.android.internal.R.styleable.GradientDrawable); 808 809 super.inflateWithAttributes(r, parser, a, 810 com.android.internal.R.styleable.GradientDrawable_visible); 811 812 int shapeType = a.getInt( 813 com.android.internal.R.styleable.GradientDrawable_shape, RECTANGLE); 814 boolean dither = a.getBoolean( 815 com.android.internal.R.styleable.GradientDrawable_dither, false); 816 817 if (shapeType == RING) { 818 st.mInnerRadius = a.getDimensionPixelSize( 819 com.android.internal.R.styleable.GradientDrawable_innerRadius, -1); 820 if (st.mInnerRadius == -1) { 821 st.mInnerRadiusRatio = a.getFloat( 822 com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f); 823 } 824 st.mThickness = a.getDimensionPixelSize( 825 com.android.internal.R.styleable.GradientDrawable_thickness, -1); 826 if (st.mThickness == -1) { 827 st.mThicknessRatio = a.getFloat( 828 com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f); 829 } 830 st.mUseLevelForShape = a.getBoolean( 831 com.android.internal.R.styleable.GradientDrawable_useLevel, true); 832 } 833 834 a.recycle(); 835 836 setShape(shapeType); 837 setDither(dither); 838 839 int type; 840 841 final int innerDepth = parser.getDepth() + 1; 842 int depth; 843 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 844 && ((depth=parser.getDepth()) >= innerDepth 845 || type != XmlPullParser.END_TAG)) { 846 if (type != XmlPullParser.START_TAG) { 847 continue; 848 } 849 850 if (depth > innerDepth) { 851 continue; 852 } 853 854 String name = parser.getName(); 855 856 if (name.equals("size")) { 857 a = r.obtainAttributes(attrs, 858 com.android.internal.R.styleable.GradientDrawableSize); 859 int width = a.getDimensionPixelSize( 860 com.android.internal.R.styleable.GradientDrawableSize_width, -1); 861 int height = a.getDimensionPixelSize( 862 com.android.internal.R.styleable.GradientDrawableSize_height, -1); 863 a.recycle(); 864 setSize(width, height); 865 } else if (name.equals("gradient")) { 866 a = r.obtainAttributes(attrs, 867 com.android.internal.R.styleable.GradientDrawableGradient); 868 int startColor = a.getColor( 869 com.android.internal.R.styleable.GradientDrawableGradient_startColor, 0); 870 boolean hasCenterColor = a 871 .hasValue(com.android.internal.R.styleable.GradientDrawableGradient_centerColor); 872 int centerColor = a.getColor( 873 com.android.internal.R.styleable.GradientDrawableGradient_centerColor, 0); 874 int endColor = a.getColor( 875 com.android.internal.R.styleable.GradientDrawableGradient_endColor, 0); 876 int gradientType = a.getInt( 877 com.android.internal.R.styleable.GradientDrawableGradient_type, 878 LINEAR_GRADIENT); 879 880 st.mCenterX = getFloatOrFraction( 881 a, 882 com.android.internal.R.styleable.GradientDrawableGradient_centerX, 883 0.5f); 884 885 st.mCenterY = getFloatOrFraction( 886 a, 887 com.android.internal.R.styleable.GradientDrawableGradient_centerY, 888 0.5f); 889 890 st.mUseLevel = a.getBoolean( 891 com.android.internal.R.styleable.GradientDrawableGradient_useLevel, false); 892 st.mGradient = gradientType; 893 894 if (gradientType == LINEAR_GRADIENT) { 895 int angle = (int)a.getFloat( 896 com.android.internal.R.styleable.GradientDrawableGradient_angle, 0); 897 angle %= 360; 898 if (angle % 45 != 0) { 899 throw new XmlPullParserException(a.getPositionDescription() 900 + "<gradient> tag requires 'angle' attribute to " 901 + "be a multiple of 45"); 902 } 903 904 switch (angle) { 905 case 0: 906 st.mOrientation = Orientation.LEFT_RIGHT; 907 break; 908 case 45: 909 st.mOrientation = Orientation.BL_TR; 910 break; 911 case 90: 912 st.mOrientation = Orientation.BOTTOM_TOP; 913 break; 914 case 135: 915 st.mOrientation = Orientation.BR_TL; 916 break; 917 case 180: 918 st.mOrientation = Orientation.RIGHT_LEFT; 919 break; 920 case 225: 921 st.mOrientation = Orientation.TR_BL; 922 break; 923 case 270: 924 st.mOrientation = Orientation.TOP_BOTTOM; 925 break; 926 case 315: 927 st.mOrientation = Orientation.TL_BR; 928 break; 929 } 930 } else { 931 TypedValue tv = a.peekValue( 932 com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius); 933 if (tv != null) { 934 boolean radiusRel = tv.type == TypedValue.TYPE_FRACTION; 935 st.mGradientRadius = radiusRel ? 936 tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 937 } else if (gradientType == RADIAL_GRADIENT) { 938 throw new XmlPullParserException( 939 a.getPositionDescription() 940 + "<gradient> tag requires 'gradientRadius' " 941 + "attribute with radial type"); 942 } 943 } 944 945 a.recycle(); 946 947 if (hasCenterColor) { 948 st.mColors = new int[3]; 949 st.mColors[0] = startColor; 950 st.mColors[1] = centerColor; 951 st.mColors[2] = endColor; 952 953 st.mPositions = new float[3]; 954 st.mPositions[0] = 0.0f; 955 // Since 0.5f is default value, try to take the one that isn't 0.5f 956 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; 957 st.mPositions[2] = 1f; 958 } else { 959 st.mColors = new int[2]; 960 st.mColors[0] = startColor; 961 st.mColors[1] = endColor; 962 } 963 964 } else if (name.equals("solid")) { 965 a = r.obtainAttributes(attrs, 966 com.android.internal.R.styleable.GradientDrawableSolid); 967 int argb = a.getColor( 968 com.android.internal.R.styleable.GradientDrawableSolid_color, 0); 969 a.recycle(); 970 setColor(argb); 971 } else if (name.equals("stroke")) { 972 a = r.obtainAttributes(attrs, 973 com.android.internal.R.styleable.GradientDrawableStroke); 974 int width = a.getDimensionPixelSize( 975 com.android.internal.R.styleable.GradientDrawableStroke_width, 0); 976 int color = a.getColor( 977 com.android.internal.R.styleable.GradientDrawableStroke_color, 0); 978 float dashWidth = a.getDimension( 979 com.android.internal.R.styleable.GradientDrawableStroke_dashWidth, 0); 980 if (dashWidth != 0.0f) { 981 float dashGap = a.getDimension( 982 com.android.internal.R.styleable.GradientDrawableStroke_dashGap, 0); 983 setStroke(width, color, dashWidth, dashGap); 984 } else { 985 setStroke(width, color); 986 } 987 a.recycle(); 988 } else if (name.equals("corners")) { 989 a = r.obtainAttributes(attrs, 990 com.android.internal.R.styleable.DrawableCorners); 991 int radius = a.getDimensionPixelSize( 992 com.android.internal.R.styleable.DrawableCorners_radius, 0); 993 setCornerRadius(radius); 994 int topLeftRadius = a.getDimensionPixelSize( 995 com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius); 996 int topRightRadius = a.getDimensionPixelSize( 997 com.android.internal.R.styleable.DrawableCorners_topRightRadius, radius); 998 int bottomLeftRadius = a.getDimensionPixelSize( 999 com.android.internal.R.styleable.DrawableCorners_bottomLeftRadius, radius); 1000 int bottomRightRadius = a.getDimensionPixelSize( 1001 com.android.internal.R.styleable.DrawableCorners_bottomRightRadius, radius); 1002 if (topLeftRadius != radius || topRightRadius != radius || 1003 bottomLeftRadius != radius || bottomRightRadius != radius) { 1004 // The corner radii are specified in clockwise order (see Path.addRoundRect()) 1005 setCornerRadii(new float[] { 1006 topLeftRadius, topLeftRadius, 1007 topRightRadius, topRightRadius, 1008 bottomRightRadius, bottomRightRadius, 1009 bottomLeftRadius, bottomLeftRadius 1010 }); 1011 } 1012 a.recycle(); 1013 } else if (name.equals("padding")) { 1014 a = r.obtainAttributes(attrs, 1015 com.android.internal.R.styleable.GradientDrawablePadding); 1016 mPadding = new Rect( 1017 a.getDimensionPixelOffset( 1018 com.android.internal.R.styleable.GradientDrawablePadding_left, 0), 1019 a.getDimensionPixelOffset( 1020 com.android.internal.R.styleable.GradientDrawablePadding_top, 0), 1021 a.getDimensionPixelOffset( 1022 com.android.internal.R.styleable.GradientDrawablePadding_right, 0), 1023 a.getDimensionPixelOffset( 1024 com.android.internal.R.styleable.GradientDrawablePadding_bottom, 0)); 1025 a.recycle(); 1026 mGradientState.mPadding = mPadding; 1027 } else { 1028 Log.w("drawable", "Bad element under <shape>: " + name); 1029 } 1030 1031 } 1032 1033 mGradientState.computeOpacity(); 1034 } 1035 1036 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { 1037 TypedValue tv = a.peekValue(index); 1038 float v = defaultValue; 1039 if (tv != null) { 1040 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; 1041 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 1042 } 1043 return v; 1044 } 1045 1046 @Override 1047 public int getIntrinsicWidth() { 1048 return mGradientState.mWidth; 1049 } 1050 1051 @Override 1052 public int getIntrinsicHeight() { 1053 return mGradientState.mHeight; 1054 } 1055 1056 @Override 1057 public ConstantState getConstantState() { 1058 mGradientState.mChangingConfigurations = getChangingConfigurations(); 1059 return mGradientState; 1060 } 1061 1062 @Override 1063 public Drawable mutate() { 1064 if (!mMutated && super.mutate() == this) { 1065 mGradientState = new GradientState(mGradientState); 1066 initializeWithState(mGradientState); 1067 mMutated = true; 1068 } 1069 return this; 1070 } 1071 1072 final static class GradientState extends ConstantState { 1073 public int mChangingConfigurations; 1074 public int mShape = RECTANGLE; 1075 public int mGradient = LINEAR_GRADIENT; 1076 public Orientation mOrientation; 1077 public int[] mColors; 1078 public int[] mTempColors; // no need to copy 1079 public float[] mTempPositions; // no need to copy 1080 public float[] mPositions; 1081 public boolean mHasSolidColor; 1082 public int mSolidColor; 1083 public int mStrokeWidth = -1; // if >= 0 use stroking. 1084 public int mStrokeColor; 1085 public float mStrokeDashWidth; 1086 public float mStrokeDashGap; 1087 public float mRadius; // use this if mRadiusArray is null 1088 public float[] mRadiusArray; 1089 public Rect mPadding; 1090 public int mWidth = -1; 1091 public int mHeight = -1; 1092 public float mInnerRadiusRatio; 1093 public float mThicknessRatio; 1094 public int mInnerRadius; 1095 public int mThickness; 1096 private float mCenterX = 0.5f; 1097 private float mCenterY = 0.5f; 1098 private float mGradientRadius = 0.5f; 1099 private boolean mUseLevel; 1100 private boolean mUseLevelForShape; 1101 private boolean mOpaque; 1102 1103 GradientState(Orientation orientation, int[] colors) { 1104 mOrientation = orientation; 1105 setColors(colors); 1106 } 1107 1108 public GradientState(GradientState state) { 1109 mChangingConfigurations = state.mChangingConfigurations; 1110 mShape = state.mShape; 1111 mGradient = state.mGradient; 1112 mOrientation = state.mOrientation; 1113 if (state.mColors != null) { 1114 mColors = state.mColors.clone(); 1115 } 1116 if (state.mPositions != null) { 1117 mPositions = state.mPositions.clone(); 1118 } 1119 mHasSolidColor = state.mHasSolidColor; 1120 mSolidColor = state.mSolidColor; 1121 mStrokeWidth = state.mStrokeWidth; 1122 mStrokeColor = state.mStrokeColor; 1123 mStrokeDashWidth = state.mStrokeDashWidth; 1124 mStrokeDashGap = state.mStrokeDashGap; 1125 mRadius = state.mRadius; 1126 if (state.mRadiusArray != null) { 1127 mRadiusArray = state.mRadiusArray.clone(); 1128 } 1129 if (state.mPadding != null) { 1130 mPadding = new Rect(state.mPadding); 1131 } 1132 mWidth = state.mWidth; 1133 mHeight = state.mHeight; 1134 mInnerRadiusRatio = state.mInnerRadiusRatio; 1135 mThicknessRatio = state.mThicknessRatio; 1136 mInnerRadius = state.mInnerRadius; 1137 mThickness = state.mThickness; 1138 mCenterX = state.mCenterX; 1139 mCenterY = state.mCenterY; 1140 mGradientRadius = state.mGradientRadius; 1141 mUseLevel = state.mUseLevel; 1142 mUseLevelForShape = state.mUseLevelForShape; 1143 mOpaque = state.mOpaque; 1144 } 1145 1146 @Override 1147 public Drawable newDrawable() { 1148 return new GradientDrawable(this); 1149 } 1150 1151 @Override 1152 public Drawable newDrawable(Resources res) { 1153 return new GradientDrawable(this); 1154 } 1155 1156 @Override 1157 public int getChangingConfigurations() { 1158 return mChangingConfigurations; 1159 } 1160 1161 public void setShape(int shape) { 1162 mShape = shape; 1163 computeOpacity(); 1164 } 1165 1166 public void setGradientType(int gradient) { 1167 mGradient = gradient; 1168 } 1169 1170 public void setGradientCenter(float x, float y) { 1171 mCenterX = x; 1172 mCenterY = y; 1173 } 1174 1175 public void setColors(int[] colors) { 1176 mHasSolidColor = false; 1177 mColors = colors; 1178 computeOpacity(); 1179 } 1180 1181 public void setSolidColor(int argb) { 1182 mHasSolidColor = true; 1183 mSolidColor = argb; 1184 mColors = null; 1185 computeOpacity(); 1186 } 1187 1188 private void computeOpacity() { 1189 if (mShape != RECTANGLE) { 1190 mOpaque = false; 1191 return; 1192 } 1193 1194 if (mRadius > 0 || mRadiusArray != null) { 1195 mOpaque = false; 1196 return; 1197 } 1198 1199 if (mStrokeWidth > 0 && !isOpaque(mStrokeColor)) { 1200 mOpaque = false; 1201 return; 1202 } 1203 1204 if (mHasSolidColor) { 1205 mOpaque = isOpaque(mSolidColor); 1206 return; 1207 } 1208 1209 if (mColors != null) { 1210 for (int i = 0; i < mColors.length; i++) { 1211 if (!isOpaque(mColors[i])) { 1212 mOpaque = false; 1213 return; 1214 } 1215 } 1216 } 1217 1218 mOpaque = true; 1219 } 1220 1221 private static boolean isOpaque(int color) { 1222 return ((color >> 24) & 0xff) == 0xff; 1223 } 1224 1225 public void setStroke(int width, int color) { 1226 mStrokeWidth = width; 1227 mStrokeColor = color; 1228 computeOpacity(); 1229 } 1230 1231 public void setStroke(int width, int color, float dashWidth, float dashGap) { 1232 mStrokeWidth = width; 1233 mStrokeColor = color; 1234 mStrokeDashWidth = dashWidth; 1235 mStrokeDashGap = dashGap; 1236 computeOpacity(); 1237 } 1238 1239 public void setCornerRadius(float radius) { 1240 if (radius < 0) { 1241 radius = 0; 1242 } 1243 mRadius = radius; 1244 mRadiusArray = null; 1245 } 1246 1247 public void setCornerRadii(float[] radii) { 1248 mRadiusArray = radii; 1249 if (radii == null) { 1250 mRadius = 0; 1251 } 1252 } 1253 1254 public void setSize(int width, int height) { 1255 mWidth = width; 1256 mHeight = height; 1257 } 1258 1259 public void setGradientRadius(float gradientRadius) { 1260 mGradientRadius = gradientRadius; 1261 } 1262 } 1263 1264 private GradientDrawable(GradientState state) { 1265 mGradientState = state; 1266 initializeWithState(state); 1267 mRectIsDirty = true; 1268 mMutated = false; 1269 } 1270 1271 private void initializeWithState(GradientState state) { 1272 if (state.mHasSolidColor) { 1273 mFillPaint.setColor(state.mSolidColor); 1274 } else if (state.mColors == null) { 1275 // If we don't have a solid color and we don't have a gradient, 1276 // the app is stroking the shape, set the color to the default 1277 // value of state.mSolidColor 1278 mFillPaint.setColor(0); 1279 } 1280 mPadding = state.mPadding; 1281 if (state.mStrokeWidth >= 0) { 1282 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1283 mStrokePaint.setStyle(Paint.Style.STROKE); 1284 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1285 mStrokePaint.setColor(state.mStrokeColor); 1286 1287 if (state.mStrokeDashWidth != 0.0f) { 1288 DashPathEffect e = new DashPathEffect( 1289 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1290 mStrokePaint.setPathEffect(e); 1291 } 1292 } 1293 } 1294} 1295