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