GradientDrawable.java revision f4c068b72e2dee2e6944488ef00b64c93217d7e8
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.content.res.Resources.Theme; 23import android.graphics.Canvas; 24import android.graphics.Color; 25import android.graphics.ColorFilter; 26import android.graphics.DashPathEffect; 27import android.graphics.LinearGradient; 28import android.graphics.Outline; 29import android.graphics.Paint; 30import android.graphics.Path; 31import android.graphics.PixelFormat; 32import android.graphics.PorterDuff; 33import android.graphics.PorterDuffColorFilter; 34import android.graphics.RadialGradient; 35import android.graphics.Rect; 36import android.graphics.RectF; 37import android.graphics.Shader; 38import android.graphics.SweepGradient; 39import android.util.AttributeSet; 40import android.util.Log; 41import android.util.TypedValue; 42 43import com.android.internal.R; 44 45import org.xmlpull.v1.XmlPullParser; 46import org.xmlpull.v1.XmlPullParserException; 47 48import java.io.IOException; 49 50/** 51 * A Drawable with a color gradient for buttons, backgrounds, etc. 52 * 53 * <p>It can be defined in an XML file with the <code><shape></code> element. For more 54 * information, see the guide to <a 55 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 56 * 57 * @attr ref android.R.styleable#GradientDrawable_visible 58 * @attr ref android.R.styleable#GradientDrawable_shape 59 * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio 60 * @attr ref android.R.styleable#GradientDrawable_innerRadius 61 * @attr ref android.R.styleable#GradientDrawable_thicknessRatio 62 * @attr ref android.R.styleable#GradientDrawable_thickness 63 * @attr ref android.R.styleable#GradientDrawable_useLevel 64 * @attr ref android.R.styleable#GradientDrawableSize_width 65 * @attr ref android.R.styleable#GradientDrawableSize_height 66 * @attr ref android.R.styleable#GradientDrawableGradient_startColor 67 * @attr ref android.R.styleable#GradientDrawableGradient_centerColor 68 * @attr ref android.R.styleable#GradientDrawableGradient_endColor 69 * @attr ref android.R.styleable#GradientDrawableGradient_useLevel 70 * @attr ref android.R.styleable#GradientDrawableGradient_angle 71 * @attr ref android.R.styleable#GradientDrawableGradient_type 72 * @attr ref android.R.styleable#GradientDrawableGradient_centerX 73 * @attr ref android.R.styleable#GradientDrawableGradient_centerY 74 * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius 75 * @attr ref android.R.styleable#GradientDrawableSolid_color 76 * @attr ref android.R.styleable#GradientDrawableStroke_width 77 * @attr ref android.R.styleable#GradientDrawableStroke_color 78 * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth 79 * @attr ref android.R.styleable#GradientDrawableStroke_dashGap 80 * @attr ref android.R.styleable#GradientDrawablePadding_left 81 * @attr ref android.R.styleable#GradientDrawablePadding_top 82 * @attr ref android.R.styleable#GradientDrawablePadding_right 83 * @attr ref android.R.styleable#GradientDrawablePadding_bottom 84 */ 85public class GradientDrawable extends Drawable { 86 /** 87 * Shape is a rectangle, possibly with rounded corners 88 */ 89 public static final int RECTANGLE = 0; 90 91 /** 92 * Shape is an ellipse 93 */ 94 public static final int OVAL = 1; 95 96 /** 97 * Shape is a line 98 */ 99 public static final int LINE = 2; 100 101 /** 102 * Shape is a ring. 103 */ 104 public static final int RING = 3; 105 106 /** 107 * Gradient is linear (default.) 108 */ 109 public static final int LINEAR_GRADIENT = 0; 110 111 /** 112 * Gradient is circular. 113 */ 114 public static final int RADIAL_GRADIENT = 1; 115 116 /** 117 * Gradient is a sweep. 118 */ 119 public static final int SWEEP_GRADIENT = 2; 120 121 /** Radius is in pixels. */ 122 private static final int RADIUS_TYPE_PIXELS = 0; 123 124 /** Radius is a fraction of the base size. */ 125 private static final int RADIUS_TYPE_FRACTION = 1; 126 127 /** Radius is a fraction of the bounds size. */ 128 private static final int RADIUS_TYPE_FRACTION_PARENT = 2; 129 130 private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f; 131 private static final float DEFAULT_THICKNESS_RATIO = 9.0f; 132 133 private GradientState mGradientState; 134 135 private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 136 private Rect mPadding; 137 private Paint mStrokePaint; // optional, set by the caller 138 private ColorFilter mColorFilter; // optional, set by the caller 139 private PorterDuffColorFilter mTintFilter; 140 private int mAlpha = 0xFF; // modified by the caller 141 142 private final Path mPath = new Path(); 143 private final RectF mRect = new RectF(); 144 145 private Paint mLayerPaint; // internal, used if we use saveLayer() 146 private boolean mGradientIsDirty; 147 private boolean mMutated; 148 private Path mRingPath; 149 private boolean mPathIsDirty = true; 150 151 /** Current gradient radius, valid when {@link #mGradientIsDirty} is false. */ 152 private float mGradientRadius; 153 154 /** 155 * Controls how the gradient is oriented relative to the drawable's bounds 156 */ 157 public enum Orientation { 158 /** draw the gradient from the top to the bottom */ 159 TOP_BOTTOM, 160 /** draw the gradient from the top-right to the bottom-left */ 161 TR_BL, 162 /** draw the gradient from the right to the left */ 163 RIGHT_LEFT, 164 /** draw the gradient from the bottom-right to the top-left */ 165 BR_TL, 166 /** draw the gradient from the bottom to the top */ 167 BOTTOM_TOP, 168 /** draw the gradient from the bottom-left to the top-right */ 169 BL_TR, 170 /** draw the gradient from the left to the right */ 171 LEFT_RIGHT, 172 /** draw the gradient from the top-left to the bottom-right */ 173 TL_BR, 174 } 175 176 public GradientDrawable() { 177 this(new GradientState(Orientation.TOP_BOTTOM, null), null); 178 } 179 180 /** 181 * Create a new gradient drawable given an orientation and an array 182 * of colors for the gradient. 183 */ 184 public GradientDrawable(Orientation orientation, int[] colors) { 185 this(new GradientState(orientation, colors), null); 186 } 187 188 @Override 189 public boolean getPadding(Rect padding) { 190 if (mPadding != null) { 191 padding.set(mPadding); 192 return true; 193 } else { 194 return super.getPadding(padding); 195 } 196 } 197 198 /** 199 * <p>Specify radii for each of the 4 corners. For each corner, the array 200 * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are ordered 201 * top-left, top-right, bottom-right, bottom-left. 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 radii 4 pairs of X and Y radius for each corner, specified in pixels. 208 * The length of this array must be >= 8 209 * 210 * @see #mutate() 211 * @see #setCornerRadii(float[]) 212 * @see #setShape(int) 213 */ 214 public void setCornerRadii(float[] radii) { 215 mGradientState.setCornerRadii(radii); 216 mPathIsDirty = true; 217 invalidateSelf(); 218 } 219 220 /** 221 * <p>Specify radius for the corners of the gradient. If this is > 0, then the 222 * drawable is drawn in a round-rectangle, rather than a rectangle. This property 223 * is honored only when the shape is of type {@link #RECTANGLE}.</p> 224 * <p><strong>Note</strong>: changing this property will affect all instances 225 * of a drawable loaded from a resource. It is recommended to invoke 226 * {@link #mutate()} before changing this property.</p> 227 * 228 * @param radius The radius in pixels of the corners of the rectangle shape 229 * 230 * @see #mutate() 231 * @see #setCornerRadii(float[]) 232 * @see #setShape(int) 233 */ 234 public void setCornerRadius(float radius) { 235 mGradientState.setCornerRadius(radius); 236 mPathIsDirty = true; 237 invalidateSelf(); 238 } 239 240 /** 241 * <p>Set the stroke width and color for the drawable. If width is zero, 242 * 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 color The color of the stroke 249 * 250 * @see #mutate() 251 * @see #setStroke(int, int, float, float) 252 */ 253 public void setStroke(int width, int color) { 254 setStroke(width, color, 0, 0); 255 } 256 257 /** 258 * <p>Set the stroke width and color state list for the drawable. If width 259 * is zero, then no stroke is drawn.</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 colorStateList The color state list of the stroke 266 * 267 * @see #mutate() 268 * @see #setStroke(int, ColorStateList, float, float) 269 */ 270 public void setStroke(int width, ColorStateList colorStateList) { 271 setStroke(width, colorStateList, 0, 0); 272 } 273 274 /** 275 * <p>Set the stroke width and color for the drawable. If width is zero, 276 * then no stroke is drawn. This method can also be used to dash 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 color The color 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, int) 288 */ 289 public void setStroke(int width, int color, float dashWidth, float dashGap) { 290 mGradientState.setStroke(width, ColorStateList.valueOf(color), dashWidth, dashGap); 291 setStrokeInternal(width, color, dashWidth, dashGap); 292 } 293 294 /** 295 * <p>Set the stroke width and color state list for the drawable. If width 296 * is zero, then no stroke is drawn. This method can also be used to dash 297 * the stroke.</p> 298 * <p><strong>Note</strong>: changing this property will affect all instances 299 * of a drawable loaded from a resource. It is recommended to invoke 300 * {@link #mutate()} before changing this property.</p> 301 * 302 * @param width The width in pixels of the stroke 303 * @param colorStateList The color state list of the stroke 304 * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes 305 * @param dashGap The gap in pixels between dashes 306 * 307 * @see #mutate() 308 * @see #setStroke(int, ColorStateList) 309 */ 310 public void setStroke( 311 int width, ColorStateList colorStateList, float dashWidth, float dashGap) { 312 mGradientState.setStroke(width, colorStateList, dashWidth, dashGap); 313 final int color; 314 if (colorStateList == null) { 315 color = Color.TRANSPARENT; 316 } else { 317 final int[] stateSet = getState(); 318 color = colorStateList.getColorForState(stateSet, 0); 319 } 320 setStrokeInternal(width, color, dashWidth, dashGap); 321 } 322 323 private void setStrokeInternal(int width, int color, float dashWidth, float dashGap) { 324 if (mStrokePaint == null) { 325 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 326 mStrokePaint.setStyle(Paint.Style.STROKE); 327 } 328 mStrokePaint.setStrokeWidth(width); 329 mStrokePaint.setColor(color); 330 331 DashPathEffect e = null; 332 if (dashWidth > 0) { 333 e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0); 334 } 335 mStrokePaint.setPathEffect(e); 336 invalidateSelf(); 337 } 338 339 340 /** 341 * <p>Sets the size of the shape drawn by this drawable.</p> 342 * <p><strong>Note</strong>: changing this property will affect all instances 343 * of a drawable loaded from a resource. It is recommended to invoke 344 * {@link #mutate()} before changing this property.</p> 345 * 346 * @param width The width of the shape used by this drawable 347 * @param height The height of the shape used by this drawable 348 * 349 * @see #mutate() 350 * @see #setGradientType(int) 351 */ 352 public void setSize(int width, int height) { 353 mGradientState.setSize(width, height); 354 mPathIsDirty = true; 355 invalidateSelf(); 356 } 357 358 /** 359 * <p>Sets the type of shape used to draw the gradient.</p> 360 * <p><strong>Note</strong>: changing this property will affect all instances 361 * of a drawable loaded from a resource. It is recommended to invoke 362 * {@link #mutate()} before changing this property.</p> 363 * 364 * @param shape The desired shape for this drawable: {@link #LINE}, 365 * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING} 366 * 367 * @see #mutate() 368 */ 369 public void setShape(int shape) { 370 mRingPath = null; 371 mPathIsDirty = true; 372 mGradientState.setShape(shape); 373 invalidateSelf(); 374 } 375 376 /** 377 * <p>Sets the type of gradient used by this drawable..</p> 378 * <p><strong>Note</strong>: changing this property will affect all instances 379 * of a drawable loaded from a resource. It is recommended to invoke 380 * {@link #mutate()} before changing this property.</p> 381 * 382 * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT}, 383 * {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT} 384 * 385 * @see #mutate() 386 */ 387 public void setGradientType(int gradient) { 388 mGradientState.setGradientType(gradient); 389 mGradientIsDirty = true; 390 invalidateSelf(); 391 } 392 393 /** 394 * <p>Sets the center location of the gradient. The radius is honored only when 395 * the gradient type is set to {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}.</p> 396 * <p><strong>Note</strong>: changing this property will affect all instances 397 * of a drawable loaded from a resource. It is recommended to invoke 398 * {@link #mutate()} before changing this property.</p> 399 * 400 * @param x The x coordinate of the gradient's center 401 * @param y The y coordinate of the gradient's center 402 * 403 * @see #mutate() 404 * @see #setGradientType(int) 405 */ 406 public void setGradientCenter(float x, float y) { 407 mGradientState.setGradientCenter(x, y); 408 mGradientIsDirty = true; 409 invalidateSelf(); 410 } 411 412 /** 413 * <p>Sets the radius of the gradient. The radius is honored only when the 414 * gradient type is set to {@link #RADIAL_GRADIENT}.</p> 415 * <p><strong>Note</strong>: changing this property will affect all instances 416 * of a drawable loaded from a resource. It is recommended to invoke 417 * {@link #mutate()} before changing this property.</p> 418 * 419 * @param gradientRadius The radius of the gradient in pixels 420 * 421 * @see #mutate() 422 * @see #setGradientType(int) 423 */ 424 public void setGradientRadius(float gradientRadius) { 425 mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX); 426 mGradientIsDirty = true; 427 invalidateSelf(); 428 } 429 430 /** 431 * Returns the radius of the gradient in pixels. The radius is valid only 432 * when the gradient type is set to {@link #RADIAL_GRADIENT}. 433 * 434 * @return Radius in pixels. 435 */ 436 public float getGradientRadius() { 437 if (mGradientState.mGradient != RADIAL_GRADIENT) { 438 return 0; 439 } 440 441 ensureValidRect(); 442 return mGradientRadius; 443 } 444 445 /** 446 * <p>Sets whether or not this drawable will honor its <code>level</code> 447 * property.</p> 448 * <p><strong>Note</strong>: changing this property will affect all instances 449 * of a drawable loaded from a resource. It is recommended to invoke 450 * {@link #mutate()} before changing this property.</p> 451 * 452 * @param useLevel True if this drawable should honor its level, false otherwise 453 * 454 * @see #mutate() 455 * @see #setLevel(int) 456 * @see #getLevel() 457 */ 458 public void setUseLevel(boolean useLevel) { 459 mGradientState.mUseLevel = useLevel; 460 mGradientIsDirty = true; 461 invalidateSelf(); 462 } 463 464 private int modulateAlpha(int alpha) { 465 int scale = mAlpha + (mAlpha >> 7); 466 return alpha * scale >> 8; 467 } 468 469 /** 470 * Returns the orientation of the gradient defined in this drawable. 471 */ 472 public Orientation getOrientation() { 473 return mGradientState.mOrientation; 474 } 475 476 /** 477 * <p>Changes the orientation of the gradient defined in this drawable.</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 orientation The desired orientation (angle) of the gradient 483 * 484 * @see #mutate() 485 */ 486 public void setOrientation(Orientation orientation) { 487 mGradientState.mOrientation = orientation; 488 mGradientIsDirty = true; 489 invalidateSelf(); 490 } 491 492 /** 493 * <p>Sets the colors used to draw the gradient. Each color is specified as an 494 * ARGB integer and the array must contain at least 2 colors.</p> 495 * <p><strong>Note</strong>: changing colors will affect all instances 496 * of a drawable loaded from a resource. It is recommended to invoke 497 * {@link #mutate()} before changing the colors.</p> 498 * 499 * @param colors 2 or more ARGB colors 500 * 501 * @see #mutate() 502 * @see #setColor(int) 503 */ 504 public void setColors(int[] colors) { 505 mGradientState.setColors(colors); 506 mGradientIsDirty = true; 507 invalidateSelf(); 508 } 509 510 @Override 511 public void draw(Canvas canvas) { 512 if (!ensureValidRect()) { 513 // nothing to draw 514 return; 515 } 516 517 // remember the alpha values, in case we temporarily overwrite them 518 // when we modulate them with mAlpha 519 final int prevFillAlpha = mFillPaint.getAlpha(); 520 final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0; 521 // compute the modulate alpha values 522 final int currFillAlpha = modulateAlpha(prevFillAlpha); 523 final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha); 524 525 final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null && 526 mStrokePaint.getStrokeWidth() > 0; 527 final boolean haveFill = currFillAlpha > 0; 528 final GradientState st = mGradientState; 529 final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter; 530 531 /* we need a layer iff we're drawing both a fill and stroke, and the 532 stroke is non-opaque, and our shapetype actually supports 533 fill+stroke. Otherwise we can just draw the stroke (if any) on top 534 of the fill (if any) without worrying about blending artifacts. 535 */ 536 final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && 537 currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null); 538 539 /* Drawing with a layer is slower than direct drawing, but it 540 allows us to apply paint effects like alpha and colorfilter to 541 the result of multiple separate draws. In our case, if the user 542 asks for a non-opaque alpha value (via setAlpha), and we're 543 stroking, then we need to apply the alpha AFTER we've drawn 544 both the fill and the stroke. 545 */ 546 if (useLayer) { 547 if (mLayerPaint == null) { 548 mLayerPaint = new Paint(); 549 } 550 mLayerPaint.setDither(st.mDither); 551 mLayerPaint.setAlpha(mAlpha); 552 mLayerPaint.setColorFilter(colorFilter); 553 554 float rad = mStrokePaint.getStrokeWidth(); 555 canvas.saveLayer(mRect.left - rad, mRect.top - rad, 556 mRect.right + rad, mRect.bottom + rad, 557 mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); 558 559 // don't perform the filter in our individual paints 560 // since the layer will do it for us 561 mFillPaint.setColorFilter(null); 562 mStrokePaint.setColorFilter(null); 563 } else { 564 /* if we're not using a layer, apply the dither/filter to our 565 individual paints 566 */ 567 mFillPaint.setAlpha(currFillAlpha); 568 mFillPaint.setDither(st.mDither); 569 mFillPaint.setColorFilter(colorFilter); 570 if (colorFilter != null && st.mColorStateList == null) { 571 mFillPaint.setColor(mAlpha << 24); 572 } 573 if (haveStroke) { 574 mStrokePaint.setAlpha(currStrokeAlpha); 575 mStrokePaint.setDither(st.mDither); 576 mStrokePaint.setColorFilter(colorFilter); 577 } 578 } 579 580 switch (st.mShape) { 581 case RECTANGLE: 582 if (st.mRadiusArray != null) { 583 buildPathIfDirty(); 584 canvas.drawPath(mPath, mFillPaint); 585 if (haveStroke) { 586 canvas.drawPath(mPath, mStrokePaint); 587 } 588 } else if (st.mRadius > 0.0f) { 589 // since the caller is only giving us 1 value, we will force 590 // it to be square if the rect is too small in one dimension 591 // to show it. If we did nothing, Skia would clamp the rad 592 // independently along each axis, giving us a thin ellipse 593 // if the rect were very wide but not very tall 594 float rad = Math.min(st.mRadius, 595 Math.min(mRect.width(), mRect.height()) * 0.5f); 596 canvas.drawRoundRect(mRect, rad, rad, mFillPaint); 597 if (haveStroke) { 598 canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); 599 } 600 } else { 601 if (mFillPaint.getColor() != 0 || colorFilter != null || 602 mFillPaint.getShader() != null) { 603 canvas.drawRect(mRect, mFillPaint); 604 } 605 if (haveStroke) { 606 canvas.drawRect(mRect, mStrokePaint); 607 } 608 } 609 break; 610 case OVAL: 611 canvas.drawOval(mRect, mFillPaint); 612 if (haveStroke) { 613 canvas.drawOval(mRect, mStrokePaint); 614 } 615 break; 616 case LINE: { 617 RectF r = mRect; 618 float y = r.centerY(); 619 if (haveStroke) { 620 canvas.drawLine(r.left, y, r.right, y, mStrokePaint); 621 } 622 break; 623 } 624 case RING: 625 Path path = buildRing(st); 626 canvas.drawPath(path, mFillPaint); 627 if (haveStroke) { 628 canvas.drawPath(path, mStrokePaint); 629 } 630 break; 631 } 632 633 if (useLayer) { 634 canvas.restore(); 635 } else { 636 mFillPaint.setAlpha(prevFillAlpha); 637 if (haveStroke) { 638 mStrokePaint.setAlpha(prevStrokeAlpha); 639 } 640 } 641 } 642 643 private void buildPathIfDirty() { 644 final GradientState st = mGradientState; 645 if (mPathIsDirty) { 646 ensureValidRect(); 647 mPath.reset(); 648 mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); 649 mPathIsDirty = false; 650 } 651 } 652 653 private Path buildRing(GradientState st) { 654 if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath; 655 mPathIsDirty = false; 656 657 float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f; 658 659 RectF bounds = new RectF(mRect); 660 661 float x = bounds.width() / 2.0f; 662 float y = bounds.height() / 2.0f; 663 664 float thickness = st.mThickness != -1 ? 665 st.mThickness : bounds.width() / st.mThicknessRatio; 666 // inner radius 667 float radius = st.mInnerRadius != -1 ? 668 st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio; 669 670 RectF innerBounds = new RectF(bounds); 671 innerBounds.inset(x - radius, y - radius); 672 673 bounds = new RectF(innerBounds); 674 bounds.inset(-thickness, -thickness); 675 676 if (mRingPath == null) { 677 mRingPath = new Path(); 678 } else { 679 mRingPath.reset(); 680 } 681 682 final Path ringPath = mRingPath; 683 // arcTo treats the sweep angle mod 360, so check for that, since we 684 // think 360 means draw the entire oval 685 if (sweep < 360 && sweep > -360) { 686 ringPath.setFillType(Path.FillType.EVEN_ODD); 687 // inner top 688 ringPath.moveTo(x + radius, y); 689 // outer top 690 ringPath.lineTo(x + radius + thickness, y); 691 // outer arc 692 ringPath.arcTo(bounds, 0.0f, sweep, false); 693 // inner arc 694 ringPath.arcTo(innerBounds, sweep, -sweep, false); 695 ringPath.close(); 696 } else { 697 // add the entire ovals 698 ringPath.addOval(bounds, Path.Direction.CW); 699 ringPath.addOval(innerBounds, Path.Direction.CCW); 700 } 701 702 return ringPath; 703 } 704 705 /** 706 * <p>Changes this drawable to use a single color instead of a gradient.</p> 707 * <p><strong>Note</strong>: changing color will affect all instances 708 * of a drawable loaded from a resource. It is recommended to invoke 709 * {@link #mutate()} before changing the color.</p> 710 * 711 * @param argb The color used to fill the shape 712 * 713 * @see #mutate() 714 * @see #setColors(int[]) 715 */ 716 public void setColor(int argb) { 717 mGradientState.setColorStateList(ColorStateList.valueOf(argb)); 718 mFillPaint.setColor(argb); 719 invalidateSelf(); 720 } 721 722 /** 723 * Changes this drawable to use a single color state list instead of a 724 * gradient. Calling this method with a null argument will clear the color 725 * and is equivalent to calling {@link #setColor(int)} with the argument 726 * {@link Color#TRANSPARENT}. 727 * <p> 728 * <strong>Note</strong>: changing color will affect all instances of a 729 * drawable loaded from a resource. It is recommended to invoke 730 * {@link #mutate()} before changing the color.</p> 731 * 732 * @param colorStateList The color state list used to fill the shape 733 * @see #mutate() 734 */ 735 public void setColor(ColorStateList colorStateList) { 736 mGradientState.setColorStateList(colorStateList); 737 final int color; 738 if (colorStateList == null) { 739 color = Color.TRANSPARENT; 740 } else { 741 final int[] stateSet = getState(); 742 color = colorStateList.getColorForState(stateSet, 0); 743 } 744 mFillPaint.setColor(color); 745 invalidateSelf(); 746 } 747 748 @Override 749 protected boolean onStateChange(int[] stateSet) { 750 boolean invalidateSelf = false; 751 752 final GradientState s = mGradientState; 753 final ColorStateList stateList = s.mColorStateList; 754 if (stateList != null) { 755 final int newColor = stateList.getColorForState(stateSet, 0); 756 final int oldColor = mFillPaint.getColor(); 757 if (oldColor != newColor) { 758 mFillPaint.setColor(newColor); 759 invalidateSelf = true; 760 } 761 } 762 763 final Paint strokePaint = mStrokePaint; 764 if (strokePaint != null) { 765 final ColorStateList strokeStateList = s.mStrokeColorStateList; 766 if (strokeStateList != null) { 767 final int newStrokeColor = strokeStateList.getColorForState(stateSet, 0); 768 final int oldStrokeColor = strokePaint.getColor(); 769 if (oldStrokeColor != newStrokeColor) { 770 strokePaint.setColor(newStrokeColor); 771 invalidateSelf = true; 772 } 773 } 774 } 775 776 if (s.mTint != null && s.mTintMode != null) { 777 mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode); 778 invalidateSelf = true; 779 } 780 781 if (invalidateSelf) { 782 invalidateSelf(); 783 return true; 784 } 785 786 return false; 787 } 788 789 @Override 790 public boolean isStateful() { 791 final GradientState s = mGradientState; 792 return super.isStateful() 793 || (s.mColorStateList != null && s.mColorStateList.isStateful()) 794 || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful()) 795 || (s.mTint != null && s.mTint.isStateful()); 796 } 797 798 @Override 799 public int getChangingConfigurations() { 800 return super.getChangingConfigurations() | mGradientState.mChangingConfigurations; 801 } 802 803 @Override 804 public void setAlpha(int alpha) { 805 if (alpha != mAlpha) { 806 mAlpha = alpha; 807 invalidateSelf(); 808 } 809 } 810 811 @Override 812 public int getAlpha() { 813 return mAlpha; 814 } 815 816 @Override 817 public void setDither(boolean dither) { 818 if (dither != mGradientState.mDither) { 819 mGradientState.mDither = dither; 820 invalidateSelf(); 821 } 822 } 823 824 @Override 825 public boolean getDither() { 826 return mGradientState.mDither; 827 } 828 829 @Override 830 public ColorFilter getColorFilter() { 831 return mColorFilter; 832 } 833 834 @Override 835 public void setColorFilter(ColorFilter cf) { 836 if (cf != mColorFilter) { 837 mColorFilter = cf; 838 invalidateSelf(); 839 } 840 } 841 842 @Override 843 public void setTintList(ColorStateList tint) { 844 mGradientState.mTint = tint; 845 mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode); 846 invalidateSelf(); 847 } 848 849 @Override 850 public void setTintMode(PorterDuff.Mode tintMode) { 851 mGradientState.mTintMode = tintMode; 852 mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode); 853 invalidateSelf(); 854 } 855 856 @Override 857 public int getOpacity() { 858 return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ? 859 PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; 860 } 861 862 @Override 863 protected void onBoundsChange(Rect r) { 864 super.onBoundsChange(r); 865 mRingPath = null; 866 mPathIsDirty = true; 867 mGradientIsDirty = true; 868 } 869 870 @Override 871 protected boolean onLevelChange(int level) { 872 super.onLevelChange(level); 873 mGradientIsDirty = true; 874 mPathIsDirty = true; 875 invalidateSelf(); 876 return true; 877 } 878 879 /** 880 * This checks mGradientIsDirty, and if it is true, recomputes both our drawing 881 * rectangle (mRect) and the gradient itself, since it depends on our 882 * rectangle too. 883 * @return true if the resulting rectangle is not empty, false otherwise 884 */ 885 private boolean ensureValidRect() { 886 if (mGradientIsDirty) { 887 mGradientIsDirty = false; 888 889 Rect bounds = getBounds(); 890 float inset = 0; 891 892 if (mStrokePaint != null) { 893 inset = mStrokePaint.getStrokeWidth() * 0.5f; 894 } 895 896 final GradientState st = mGradientState; 897 898 mRect.set(bounds.left + inset, bounds.top + inset, 899 bounds.right - inset, bounds.bottom - inset); 900 901 final int[] colors = st.mColors; 902 if (colors != null) { 903 RectF r = mRect; 904 float x0, x1, y0, y1; 905 906 if (st.mGradient == LINEAR_GRADIENT) { 907 final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; 908 switch (st.mOrientation) { 909 case TOP_BOTTOM: 910 x0 = r.left; y0 = r.top; 911 x1 = x0; y1 = level * r.bottom; 912 break; 913 case TR_BL: 914 x0 = r.right; y0 = r.top; 915 x1 = level * r.left; y1 = level * r.bottom; 916 break; 917 case RIGHT_LEFT: 918 x0 = r.right; y0 = r.top; 919 x1 = level * r.left; y1 = y0; 920 break; 921 case BR_TL: 922 x0 = r.right; y0 = r.bottom; 923 x1 = level * r.left; y1 = level * r.top; 924 break; 925 case BOTTOM_TOP: 926 x0 = r.left; y0 = r.bottom; 927 x1 = x0; y1 = level * r.top; 928 break; 929 case BL_TR: 930 x0 = r.left; y0 = r.bottom; 931 x1 = level * r.right; y1 = level * r.top; 932 break; 933 case LEFT_RIGHT: 934 x0 = r.left; y0 = r.top; 935 x1 = level * r.right; y1 = y0; 936 break; 937 default:/* TL_BR */ 938 x0 = r.left; y0 = r.top; 939 x1 = level * r.right; y1 = level * r.bottom; 940 break; 941 } 942 943 mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, 944 colors, st.mPositions, Shader.TileMode.CLAMP)); 945 } else if (st.mGradient == RADIAL_GRADIENT) { 946 x0 = r.left + (r.right - r.left) * st.mCenterX; 947 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 948 949 float radius = st.mGradientRadius; 950 if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) { 951 // Fall back to parent width or height if intrinsic 952 // size is not specified. 953 final float width = st.mWidth >= 0 ? st.mWidth : r.width(); 954 final float height = st.mHeight >= 0 ? st.mHeight : r.height(); 955 radius *= Math.min(width, height); 956 } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) { 957 radius *= Math.min(r.width(), r.height()); 958 } 959 960 if (st.mUseLevel) { 961 radius *= getLevel() / 10000.0f; 962 } 963 964 mGradientRadius = radius; 965 966 if (radius <= 0) { 967 // We can't have a shader with non-positive radius, so 968 // let's have a very, very small radius. 969 radius = 0.001f; 970 } 971 972 mFillPaint.setShader(new RadialGradient( 973 x0, y0, radius, colors, null, Shader.TileMode.CLAMP)); 974 } else if (st.mGradient == SWEEP_GRADIENT) { 975 x0 = r.left + (r.right - r.left) * st.mCenterX; 976 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 977 978 int[] tempColors = colors; 979 float[] tempPositions = null; 980 981 if (st.mUseLevel) { 982 tempColors = st.mTempColors; 983 final int length = colors.length; 984 if (tempColors == null || tempColors.length != length + 1) { 985 tempColors = st.mTempColors = new int[length + 1]; 986 } 987 System.arraycopy(colors, 0, tempColors, 0, length); 988 tempColors[length] = colors[length - 1]; 989 990 tempPositions = st.mTempPositions; 991 final float fraction = 1.0f / (length - 1); 992 if (tempPositions == null || tempPositions.length != length + 1) { 993 tempPositions = st.mTempPositions = new float[length + 1]; 994 } 995 996 final float level = getLevel() / 10000.0f; 997 for (int i = 0; i < length; i++) { 998 tempPositions[i] = i * fraction * level; 999 } 1000 tempPositions[length] = 1.0f; 1001 1002 } 1003 mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); 1004 } 1005 1006 // If we don't have a solid color, the alpha channel must be 1007 // maxed out so that alpha modulation works correctly. 1008 if (st.mColorStateList == null) { 1009 mFillPaint.setColor(Color.BLACK); 1010 } 1011 } 1012 } 1013 return !mRect.isEmpty(); 1014 } 1015 1016 @Override 1017 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 1018 throws XmlPullParserException, IOException { 1019 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable); 1020 super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible); 1021 updateStateFromTypedArray(a); 1022 a.recycle(); 1023 1024 inflateChildElements(r, parser, attrs, theme); 1025 1026 updateLocalState(r); 1027 } 1028 1029 @Override 1030 public void applyTheme(Theme t) { 1031 super.applyTheme(t); 1032 1033 final GradientState state = mGradientState; 1034 if (state == null) { 1035 return; 1036 } 1037 1038 if (state.mThemeAttrs != null) { 1039 final TypedArray a = t.resolveAttributes( 1040 state.mThemeAttrs, R.styleable.GradientDrawable); 1041 updateStateFromTypedArray(a); 1042 a.recycle(); 1043 } 1044 1045 if (state.mTint != null && state.mTint.canApplyTheme()) { 1046 state.mTint.applyTheme(t); 1047 } 1048 1049 if (state.mColorStateList != null && state.mColorStateList.canApplyTheme()) { 1050 state.mColorStateList.applyTheme(t); 1051 } 1052 1053 if (state.mStrokeColorStateList != null && state.mStrokeColorStateList.canApplyTheme()) { 1054 state.mStrokeColorStateList.applyTheme(t); 1055 } 1056 1057 applyThemeChildElements(t); 1058 1059 updateLocalState(t.getResources()); 1060 } 1061 1062 /** 1063 * Updates the constant state from the values in the typed array. 1064 */ 1065 private void updateStateFromTypedArray(TypedArray a) { 1066 final GradientState state = mGradientState; 1067 1068 // Account for any configuration changes. 1069 state.mChangingConfigurations |= a.getChangingConfigurations(); 1070 1071 // Extract the theme attributes, if any. 1072 state.mThemeAttrs = a.extractThemeAttrs(); 1073 1074 state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape); 1075 state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither); 1076 1077 if (state.mShape == RING) { 1078 state.mInnerRadius = a.getDimensionPixelSize( 1079 R.styleable.GradientDrawable_innerRadius, state.mInnerRadius); 1080 1081 if (state.mInnerRadius == -1) { 1082 state.mInnerRadiusRatio = a.getFloat( 1083 R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio); 1084 } 1085 1086 state.mThickness = a.getDimensionPixelSize( 1087 R.styleable.GradientDrawable_thickness, state.mThickness); 1088 1089 if (state.mThickness == -1) { 1090 state.mThicknessRatio = a.getFloat( 1091 R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio); 1092 } 1093 1094 state.mUseLevelForShape = a.getBoolean( 1095 R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); 1096 } 1097 1098 final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1); 1099 if (tintMode != -1) { 1100 state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN); 1101 } 1102 1103 final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint); 1104 if (tint != null) { 1105 state.mTint = tint; 1106 } 1107 } 1108 1109 @Override 1110 public boolean canApplyTheme() { 1111 return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme(); 1112 } 1113 1114 private void applyThemeChildElements(Theme t) { 1115 final GradientState st = mGradientState; 1116 1117 if (st.mAttrSize != null) { 1118 final TypedArray a = t.resolveAttributes( 1119 st.mAttrSize, R.styleable.GradientDrawableSize); 1120 updateGradientDrawableSize(a); 1121 a.recycle(); 1122 } 1123 1124 if (st.mAttrGradient != null) { 1125 final TypedArray a = t.resolveAttributes( 1126 st.mAttrGradient, R.styleable.GradientDrawableGradient); 1127 try { 1128 updateGradientDrawableGradient(t.getResources(), a); 1129 } catch (XmlPullParserException e) { 1130 throw new RuntimeException(e); 1131 } finally { 1132 a.recycle(); 1133 } 1134 } 1135 1136 if (st.mAttrSolid != null) { 1137 final TypedArray a = t.resolveAttributes( 1138 st.mAttrSolid, R.styleable.GradientDrawableSolid); 1139 updateGradientDrawableSolid(a); 1140 a.recycle(); 1141 } 1142 1143 if (st.mAttrStroke != null) { 1144 final TypedArray a = t.resolveAttributes( 1145 st.mAttrStroke, R.styleable.GradientDrawableStroke); 1146 updateGradientDrawableStroke(a); 1147 a.recycle(); 1148 } 1149 1150 if (st.mAttrCorners != null) { 1151 final TypedArray a = t.resolveAttributes( 1152 st.mAttrCorners, R.styleable.DrawableCorners); 1153 updateDrawableCorners(a); 1154 a.recycle(); 1155 } 1156 1157 if (st.mAttrPadding != null) { 1158 final TypedArray a = t.resolveAttributes( 1159 st.mAttrPadding, R.styleable.GradientDrawablePadding); 1160 updateGradientDrawablePadding(a); 1161 a.recycle(); 1162 } 1163 } 1164 1165 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 1166 Theme theme) throws XmlPullParserException, IOException { 1167 TypedArray a; 1168 int type; 1169 1170 final int innerDepth = parser.getDepth() + 1; 1171 int depth; 1172 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1173 && ((depth=parser.getDepth()) >= innerDepth 1174 || type != XmlPullParser.END_TAG)) { 1175 if (type != XmlPullParser.START_TAG) { 1176 continue; 1177 } 1178 1179 if (depth > innerDepth) { 1180 continue; 1181 } 1182 1183 String name = parser.getName(); 1184 1185 if (name.equals("size")) { 1186 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize); 1187 updateGradientDrawableSize(a); 1188 a.recycle(); 1189 } else if (name.equals("gradient")) { 1190 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient); 1191 updateGradientDrawableGradient(r, a); 1192 a.recycle(); 1193 } else if (name.equals("solid")) { 1194 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid); 1195 updateGradientDrawableSolid(a); 1196 a.recycle(); 1197 } else if (name.equals("stroke")) { 1198 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke); 1199 updateGradientDrawableStroke(a); 1200 a.recycle(); 1201 } else if (name.equals("corners")) { 1202 a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners); 1203 updateDrawableCorners(a); 1204 a.recycle(); 1205 } else if (name.equals("padding")) { 1206 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding); 1207 updateGradientDrawablePadding(a); 1208 a.recycle(); 1209 } else { 1210 Log.w("drawable", "Bad element under <shape>: " + name); 1211 } 1212 } 1213 } 1214 1215 private void updateGradientDrawablePadding(TypedArray a) { 1216 final GradientState st = mGradientState; 1217 1218 // Account for any configuration changes. 1219 st.mChangingConfigurations |= a.getChangingConfigurations(); 1220 1221 // Extract the theme attributes, if any. 1222 st.mAttrPadding = a.extractThemeAttrs(); 1223 1224 if (st.mPadding == null) { 1225 st.mPadding = new Rect(); 1226 } 1227 1228 final Rect pad = st.mPadding; 1229 pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left), 1230 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top), 1231 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right), 1232 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom)); 1233 mPadding = pad; 1234 } 1235 1236 private void updateDrawableCorners(TypedArray a) { 1237 final GradientState st = mGradientState; 1238 1239 // Account for any configuration changes. 1240 st.mChangingConfigurations |= a.getChangingConfigurations(); 1241 1242 // Extract the theme attributes, if any. 1243 st.mAttrCorners = a.extractThemeAttrs(); 1244 1245 final int radius = a.getDimensionPixelSize( 1246 R.styleable.DrawableCorners_radius, (int) st.mRadius); 1247 setCornerRadius(radius); 1248 1249 // TODO: Update these to be themeable. 1250 final int topLeftRadius = a.getDimensionPixelSize( 1251 R.styleable.DrawableCorners_topLeftRadius, radius); 1252 final int topRightRadius = a.getDimensionPixelSize( 1253 R.styleable.DrawableCorners_topRightRadius, radius); 1254 final int bottomLeftRadius = a.getDimensionPixelSize( 1255 R.styleable.DrawableCorners_bottomLeftRadius, radius); 1256 final int bottomRightRadius = a.getDimensionPixelSize( 1257 R.styleable.DrawableCorners_bottomRightRadius, radius); 1258 if (topLeftRadius != radius || topRightRadius != radius || 1259 bottomLeftRadius != radius || bottomRightRadius != radius) { 1260 // The corner radii are specified in clockwise order (see Path.addRoundRect()) 1261 setCornerRadii(new float[] { 1262 topLeftRadius, topLeftRadius, 1263 topRightRadius, topRightRadius, 1264 bottomRightRadius, bottomRightRadius, 1265 bottomLeftRadius, bottomLeftRadius 1266 }); 1267 } 1268 } 1269 1270 private void updateGradientDrawableStroke(TypedArray a) { 1271 final GradientState st = mGradientState; 1272 1273 // Account for any configuration changes. 1274 st.mChangingConfigurations |= a.getChangingConfigurations(); 1275 1276 // Extract the theme attributes, if any. 1277 st.mAttrStroke = a.extractThemeAttrs(); 1278 1279 // We have an explicit stroke defined, so the default stroke width 1280 // must be at least 0 or the current stroke width. 1281 final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth); 1282 final int width = a.getDimensionPixelSize( 1283 R.styleable.GradientDrawableStroke_width, defaultStrokeWidth); 1284 final float dashWidth = a.getDimension( 1285 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth); 1286 1287 ColorStateList colorStateList = a.getColorStateList( 1288 R.styleable.GradientDrawableStroke_color); 1289 if (colorStateList == null) { 1290 colorStateList = st.mStrokeColorStateList; 1291 } 1292 1293 if (dashWidth != 0.0f) { 1294 final float dashGap = a.getDimension( 1295 R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap); 1296 setStroke(width, colorStateList, dashWidth, dashGap); 1297 } else { 1298 setStroke(width, colorStateList); 1299 } 1300 } 1301 1302 private void updateGradientDrawableSolid(TypedArray a) { 1303 final GradientState st = mGradientState; 1304 1305 // Account for any configuration changes. 1306 st.mChangingConfigurations |= a.getChangingConfigurations(); 1307 1308 // Extract the theme attributes, if any. 1309 st.mAttrSolid = a.extractThemeAttrs(); 1310 1311 final ColorStateList colorStateList = a.getColorStateList( 1312 R.styleable.GradientDrawableSolid_color); 1313 if (colorStateList != null) { 1314 setColor(colorStateList); 1315 } 1316 } 1317 1318 private void updateGradientDrawableGradient(Resources r, TypedArray a) 1319 throws XmlPullParserException { 1320 final GradientState st = mGradientState; 1321 1322 // Account for any configuration changes. 1323 st.mChangingConfigurations |= a.getChangingConfigurations(); 1324 1325 // Extract the theme attributes, if any. 1326 st.mAttrGradient = a.extractThemeAttrs(); 1327 1328 st.mCenterX = getFloatOrFraction( 1329 a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX); 1330 st.mCenterY = getFloatOrFraction( 1331 a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY); 1332 st.mUseLevel = a.getBoolean( 1333 R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel); 1334 st.mGradient = a.getInt( 1335 R.styleable.GradientDrawableGradient_type, st.mGradient); 1336 1337 // TODO: Update these to be themeable. 1338 final int startColor = a.getColor( 1339 R.styleable.GradientDrawableGradient_startColor, 0); 1340 final boolean hasCenterColor = a.hasValue( 1341 R.styleable.GradientDrawableGradient_centerColor); 1342 final int centerColor = a.getColor( 1343 R.styleable.GradientDrawableGradient_centerColor, 0); 1344 final int endColor = a.getColor( 1345 R.styleable.GradientDrawableGradient_endColor, 0); 1346 1347 if (hasCenterColor) { 1348 st.mColors = new int[3]; 1349 st.mColors[0] = startColor; 1350 st.mColors[1] = centerColor; 1351 st.mColors[2] = endColor; 1352 1353 st.mPositions = new float[3]; 1354 st.mPositions[0] = 0.0f; 1355 // Since 0.5f is default value, try to take the one that isn't 0.5f 1356 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; 1357 st.mPositions[2] = 1f; 1358 } else { 1359 st.mColors = new int[2]; 1360 st.mColors[0] = startColor; 1361 st.mColors[1] = endColor; 1362 } 1363 1364 if (st.mGradient == LINEAR_GRADIENT) { 1365 int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); 1366 angle %= 360; 1367 1368 if (angle % 45 != 0) { 1369 throw new XmlPullParserException(a.getPositionDescription() 1370 + "<gradient> tag requires 'angle' attribute to " 1371 + "be a multiple of 45"); 1372 } 1373 1374 st.mAngle = angle; 1375 1376 switch (angle) { 1377 case 0: 1378 st.mOrientation = Orientation.LEFT_RIGHT; 1379 break; 1380 case 45: 1381 st.mOrientation = Orientation.BL_TR; 1382 break; 1383 case 90: 1384 st.mOrientation = Orientation.BOTTOM_TOP; 1385 break; 1386 case 135: 1387 st.mOrientation = Orientation.BR_TL; 1388 break; 1389 case 180: 1390 st.mOrientation = Orientation.RIGHT_LEFT; 1391 break; 1392 case 225: 1393 st.mOrientation = Orientation.TR_BL; 1394 break; 1395 case 270: 1396 st.mOrientation = Orientation.TOP_BOTTOM; 1397 break; 1398 case 315: 1399 st.mOrientation = Orientation.TL_BR; 1400 break; 1401 } 1402 } else { 1403 final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); 1404 if (tv != null) { 1405 final float radius; 1406 final int radiusType; 1407 if (tv.type == TypedValue.TYPE_FRACTION) { 1408 radius = tv.getFraction(1.0f, 1.0f); 1409 1410 final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) 1411 & TypedValue.COMPLEX_UNIT_MASK; 1412 if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { 1413 radiusType = RADIUS_TYPE_FRACTION_PARENT; 1414 } else { 1415 radiusType = RADIUS_TYPE_FRACTION; 1416 } 1417 } else if (tv.type == TypedValue.TYPE_DIMENSION) { 1418 radius = tv.getDimension(r.getDisplayMetrics()); 1419 radiusType = RADIUS_TYPE_PIXELS; 1420 } else { 1421 radius = tv.getFloat(); 1422 radiusType = RADIUS_TYPE_PIXELS; 1423 } 1424 1425 st.mGradientRadius = radius; 1426 st.mGradientRadiusType = radiusType; 1427 } else if (st.mGradient == RADIAL_GRADIENT) { 1428 throw new XmlPullParserException( 1429 a.getPositionDescription() 1430 + "<gradient> tag requires 'gradientRadius' " 1431 + "attribute with radial type"); 1432 } 1433 } 1434 } 1435 1436 private void updateGradientDrawableSize(TypedArray a) { 1437 final GradientState st = mGradientState; 1438 1439 // Account for any configuration changes. 1440 st.mChangingConfigurations |= a.getChangingConfigurations(); 1441 1442 // Extract the theme attributes, if any. 1443 st.mAttrSize = a.extractThemeAttrs(); 1444 1445 st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth); 1446 st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight); 1447 } 1448 1449 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { 1450 TypedValue tv = a.peekValue(index); 1451 float v = defaultValue; 1452 if (tv != null) { 1453 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; 1454 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 1455 } 1456 return v; 1457 } 1458 1459 @Override 1460 public int getIntrinsicWidth() { 1461 return mGradientState.mWidth; 1462 } 1463 1464 @Override 1465 public int getIntrinsicHeight() { 1466 return mGradientState.mHeight; 1467 } 1468 1469 @Override 1470 public ConstantState getConstantState() { 1471 mGradientState.mChangingConfigurations = getChangingConfigurations(); 1472 return mGradientState; 1473 } 1474 1475 private boolean isOpaqueForState() { 1476 if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null 1477 && !isOpaque(mStrokePaint.getColor())) { 1478 return false; 1479 } 1480 1481 if (!isOpaque(mFillPaint.getColor())) { 1482 return false; 1483 } 1484 1485 return true; 1486 } 1487 1488 @Override 1489 public void getOutline(Outline outline) { 1490 final GradientState st = mGradientState; 1491 final Rect bounds = getBounds(); 1492 // only report non-zero alpha if shape being drawn is opaque 1493 outline.setAlpha(st.mOpaqueOverShape && isOpaqueForState() ? (mAlpha / 255.0f) : 0.0f); 1494 1495 switch (st.mShape) { 1496 case RECTANGLE: 1497 if (st.mRadiusArray != null) { 1498 buildPathIfDirty(); 1499 outline.setConvexPath(mPath); 1500 return; 1501 } 1502 1503 float rad = 0; 1504 if (st.mRadius > 0.0f) { 1505 // clamp the radius based on width & height, matching behavior in draw() 1506 rad = Math.min(st.mRadius, 1507 Math.min(bounds.width(), bounds.height()) * 0.5f); 1508 } 1509 outline.setRoundRect(bounds, rad); 1510 return; 1511 case OVAL: 1512 outline.setOval(bounds); 1513 return; 1514 case LINE: 1515 // Hairlines (0-width stroke) must have a non-empty outline for 1516 // shadows to draw correctly, so we'll use a very small width. 1517 final float halfStrokeWidth = mStrokePaint == null ? 1518 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f; 1519 final float centerY = bounds.centerY(); 1520 final int top = (int) Math.floor(centerY - halfStrokeWidth); 1521 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth); 1522 1523 outline.setRect(bounds.left, top, bounds.right, bottom); 1524 return; 1525 default: 1526 // TODO: support more complex shapes 1527 } 1528 } 1529 1530 @Override 1531 public Drawable mutate() { 1532 if (!mMutated && super.mutate() == this) { 1533 mGradientState = new GradientState(mGradientState); 1534 updateLocalState(null); 1535 mMutated = true; 1536 } 1537 return this; 1538 } 1539 1540 /** 1541 * @hide 1542 */ 1543 public void clearMutated() { 1544 super.clearMutated(); 1545 mMutated = false; 1546 } 1547 1548 final static class GradientState extends ConstantState { 1549 public int mChangingConfigurations; 1550 public int mShape = RECTANGLE; 1551 public int mGradient = LINEAR_GRADIENT; 1552 public int mAngle = 0; 1553 public Orientation mOrientation; 1554 public ColorStateList mColorStateList; 1555 public ColorStateList mStrokeColorStateList; 1556 public int[] mColors; 1557 public int[] mTempColors; // no need to copy 1558 public float[] mTempPositions; // no need to copy 1559 public float[] mPositions; 1560 public int mStrokeWidth = -1; // if >= 0 use stroking. 1561 public float mStrokeDashWidth = 0.0f; 1562 public float mStrokeDashGap = 0.0f; 1563 public float mRadius = 0.0f; // use this if mRadiusArray is null 1564 public float[] mRadiusArray = null; 1565 public Rect mPadding = null; 1566 public int mWidth = -1; 1567 public int mHeight = -1; 1568 public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; 1569 public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; 1570 public int mInnerRadius = -1; 1571 public int mThickness = -1; 1572 public boolean mDither = false; 1573 1574 float mCenterX = 0.5f; 1575 float mCenterY = 0.5f; 1576 float mGradientRadius = 0.5f; 1577 int mGradientRadiusType = RADIUS_TYPE_PIXELS; 1578 boolean mUseLevel = false; 1579 boolean mUseLevelForShape = true; 1580 1581 boolean mOpaqueOverBounds; 1582 boolean mOpaqueOverShape; 1583 1584 ColorStateList mTint = null; 1585 PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 1586 1587 int[] mThemeAttrs; 1588 int[] mAttrSize; 1589 int[] mAttrGradient; 1590 int[] mAttrSolid; 1591 int[] mAttrStroke; 1592 int[] mAttrCorners; 1593 int[] mAttrPadding; 1594 1595 public GradientState(Orientation orientation, int[] colors) { 1596 mOrientation = orientation; 1597 setColors(colors); 1598 } 1599 1600 public GradientState(GradientState state) { 1601 mChangingConfigurations = state.mChangingConfigurations; 1602 mShape = state.mShape; 1603 mGradient = state.mGradient; 1604 mAngle = state.mAngle; 1605 mOrientation = state.mOrientation; 1606 mColorStateList = state.mColorStateList; 1607 if (state.mColors != null) { 1608 mColors = state.mColors.clone(); 1609 } 1610 if (state.mPositions != null) { 1611 mPositions = state.mPositions.clone(); 1612 } 1613 mStrokeColorStateList = state.mStrokeColorStateList; 1614 mStrokeWidth = state.mStrokeWidth; 1615 mStrokeDashWidth = state.mStrokeDashWidth; 1616 mStrokeDashGap = state.mStrokeDashGap; 1617 mRadius = state.mRadius; 1618 if (state.mRadiusArray != null) { 1619 mRadiusArray = state.mRadiusArray.clone(); 1620 } 1621 if (state.mPadding != null) { 1622 mPadding = new Rect(state.mPadding); 1623 } 1624 mWidth = state.mWidth; 1625 mHeight = state.mHeight; 1626 mInnerRadiusRatio = state.mInnerRadiusRatio; 1627 mThicknessRatio = state.mThicknessRatio; 1628 mInnerRadius = state.mInnerRadius; 1629 mThickness = state.mThickness; 1630 mDither = state.mDither; 1631 mCenterX = state.mCenterX; 1632 mCenterY = state.mCenterY; 1633 mGradientRadius = state.mGradientRadius; 1634 mGradientRadiusType = state.mGradientRadiusType; 1635 mUseLevel = state.mUseLevel; 1636 mUseLevelForShape = state.mUseLevelForShape; 1637 mOpaqueOverBounds = state.mOpaqueOverBounds; 1638 mOpaqueOverShape = state.mOpaqueOverShape; 1639 mTint = state.mTint; 1640 mTintMode = state.mTintMode; 1641 mThemeAttrs = state.mThemeAttrs; 1642 mAttrSize = state.mAttrSize; 1643 mAttrGradient = state.mAttrGradient; 1644 mAttrSolid = state.mAttrSolid; 1645 mAttrStroke = state.mAttrStroke; 1646 mAttrCorners = state.mAttrCorners; 1647 mAttrPadding = state.mAttrPadding; 1648 } 1649 1650 @Override 1651 public boolean canApplyTheme() { 1652 return mThemeAttrs != null 1653 || mAttrSize != null || mAttrGradient != null 1654 || mAttrSolid != null || mAttrStroke != null 1655 || mAttrCorners != null || mAttrPadding != null 1656 || (mTint != null && mTint.canApplyTheme()) 1657 || (mStrokeColorStateList != null && mStrokeColorStateList.canApplyTheme()) 1658 || (mColorStateList != null && mColorStateList.canApplyTheme()) 1659 || super.canApplyTheme(); 1660 } 1661 1662 @Override 1663 public Drawable newDrawable() { 1664 return new GradientDrawable(this, null); 1665 } 1666 1667 @Override 1668 public Drawable newDrawable(Resources res) { 1669 return new GradientDrawable(this, res); 1670 } 1671 1672 @Override 1673 public int getChangingConfigurations() { 1674 return mChangingConfigurations; 1675 } 1676 1677 public void setShape(int shape) { 1678 mShape = shape; 1679 computeOpacity(); 1680 } 1681 1682 public void setGradientType(int gradient) { 1683 mGradient = gradient; 1684 } 1685 1686 public void setGradientCenter(float x, float y) { 1687 mCenterX = x; 1688 mCenterY = y; 1689 } 1690 1691 public void setColors(int[] colors) { 1692 mColors = colors; 1693 mColorStateList = null; 1694 computeOpacity(); 1695 } 1696 1697 public void setColorStateList(ColorStateList colorStateList) { 1698 mColors = null; 1699 mColorStateList = colorStateList; 1700 computeOpacity(); 1701 } 1702 1703 private void computeOpacity() { 1704 mOpaqueOverBounds = false; 1705 mOpaqueOverShape = false; 1706 1707 if (mColors != null) { 1708 for (int i = 0; i < mColors.length; i++) { 1709 if (!isOpaque(mColors[i])) { 1710 return; 1711 } 1712 } 1713 } 1714 1715 // An unfilled shape is not opaque over bounds or shape 1716 if (mColors == null && mColorStateList == null) { 1717 return; 1718 } 1719 1720 // Colors are opaque, so opaqueOverShape=true, 1721 mOpaqueOverShape = true; 1722 // and opaqueOverBounds=true if shape fills bounds 1723 mOpaqueOverBounds = mShape == RECTANGLE 1724 && mRadius <= 0 1725 && mRadiusArray == null; 1726 } 1727 1728 public void setStroke( 1729 int width, ColorStateList colorStateList, float dashWidth, float dashGap) { 1730 mStrokeWidth = width; 1731 mStrokeColorStateList = colorStateList; 1732 mStrokeDashWidth = dashWidth; 1733 mStrokeDashGap = dashGap; 1734 computeOpacity(); 1735 } 1736 1737 public void setCornerRadius(float radius) { 1738 if (radius < 0) { 1739 radius = 0; 1740 } 1741 mRadius = radius; 1742 mRadiusArray = null; 1743 } 1744 1745 public void setCornerRadii(float[] radii) { 1746 mRadiusArray = radii; 1747 if (radii == null) { 1748 mRadius = 0; 1749 } 1750 } 1751 1752 public void setSize(int width, int height) { 1753 mWidth = width; 1754 mHeight = height; 1755 } 1756 1757 public void setGradientRadius(float gradientRadius, int type) { 1758 mGradientRadius = gradientRadius; 1759 mGradientRadiusType = type; 1760 } 1761 } 1762 1763 static boolean isOpaque(int color) { 1764 return ((color >> 24) & 0xff) == 0xff; 1765 } 1766 1767 /** 1768 * Creates a new themed GradientDrawable based on the specified constant state. 1769 * <p> 1770 * The resulting drawable is guaranteed to have a new constant state. 1771 * 1772 * @param state Constant state from which the drawable inherits 1773 */ 1774 private GradientDrawable(GradientState state, Resources res) { 1775 mGradientState = state; 1776 1777 updateLocalState(res); 1778 } 1779 1780 private void updateLocalState(Resources res) { 1781 final GradientState state = mGradientState; 1782 1783 if (state.mColorStateList != null) { 1784 final int[] currentState = getState(); 1785 final int stateColor = state.mColorStateList.getColorForState(currentState, 0); 1786 mFillPaint.setColor(stateColor); 1787 } else if (state.mColors == null) { 1788 // If we don't have a solid color and we don't have a gradient, 1789 // the app is stroking the shape, set the color to the default 1790 // value of state.mSolidColor 1791 mFillPaint.setColor(0); 1792 } else { 1793 // Otherwise, make sure the fill alpha is maxed out. 1794 mFillPaint.setColor(Color.BLACK); 1795 } 1796 1797 mPadding = state.mPadding; 1798 1799 if (state.mStrokeWidth >= 0) { 1800 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1801 mStrokePaint.setStyle(Paint.Style.STROKE); 1802 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1803 1804 if (state.mStrokeColorStateList != null) { 1805 final int[] currentState = getState(); 1806 final int strokeStateColor = state.mStrokeColorStateList.getColorForState( 1807 currentState, 0); 1808 mStrokePaint.setColor(strokeStateColor); 1809 } 1810 1811 if (state.mStrokeDashWidth != 0.0f) { 1812 final DashPathEffect e = new DashPathEffect( 1813 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1814 mStrokePaint.setPathEffect(e); 1815 } 1816 } 1817 1818 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 1819 mGradientIsDirty = true; 1820 1821 state.computeOpacity(); 1822 } 1823} 1824