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