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