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