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