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