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