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