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