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