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