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