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