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