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