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