GradientDrawable.java revision 373954a1fd371a23c420aeeea9ccbc4d983733d7
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.content.res.ColorStateList; 21import android.content.res.Resources; 22import android.content.res.TypedArray; 23import android.content.res.Resources.Theme; 24import android.graphics.Canvas; 25import android.graphics.Color; 26import android.graphics.ColorFilter; 27import android.graphics.DashPathEffect; 28import android.graphics.Insets; 29import android.graphics.LinearGradient; 30import android.graphics.Outline; 31import android.graphics.Paint; 32import android.graphics.Path; 33import android.graphics.PixelFormat; 34import android.graphics.PorterDuff; 35import android.graphics.PorterDuffColorFilter; 36import android.graphics.RadialGradient; 37import android.graphics.Rect; 38import android.graphics.RectF; 39import android.graphics.Shader; 40import android.graphics.SweepGradient; 41import android.util.AttributeSet; 42import android.util.Log; 43import android.util.TypedValue; 44 45import com.android.internal.R; 46 47import org.xmlpull.v1.XmlPullParser; 48import org.xmlpull.v1.XmlPullParserException; 49 50import 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 */ 87public 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 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 */ 186 public GradientDrawable(Orientation orientation, @ColorInt int[] colors) { 187 this(new GradientState(orientation, colors), null); 188 } 189 190 @Override 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 460 public void setUseLevel(boolean useLevel) { 461 mGradientState.mUseLevel = useLevel; 462 mGradientIsDirty = true; 463 invalidateSelf(); 464 } 465 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 */ 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 */ 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 */ 508 public void setColors(@ColorInt int[] colors) { 509 mGradientState.setGradientColors(colors); 510 mGradientIsDirty = true; 511 invalidateSelf(); 512 } 513 514 @Override 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 boolean isDither() { 830 return mGradientState.mDither; 831 } 832 833 @Override 834 public ColorFilter getColorFilter() { 835 return mColorFilter; 836 } 837 838 @Override 839 public void setColorFilter(ColorFilter colorFilter) { 840 if (colorFilter != mColorFilter) { 841 mColorFilter = colorFilter; 842 invalidateSelf(); 843 } 844 } 845 846 @Override 847 public void setTintList(ColorStateList tint) { 848 mGradientState.mTint = tint; 849 mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode); 850 invalidateSelf(); 851 } 852 853 @Override 854 public void setTintMode(PorterDuff.Mode tintMode) { 855 mGradientState.mTintMode = tintMode; 856 mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode); 857 invalidateSelf(); 858 } 859 860 @Override 861 public int getOpacity() { 862 return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ? 863 PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; 864 } 865 866 @Override 867 protected void onBoundsChange(Rect r) { 868 super.onBoundsChange(r); 869 mRingPath = null; 870 mPathIsDirty = true; 871 mGradientIsDirty = true; 872 } 873 874 @Override 875 protected boolean onLevelChange(int level) { 876 super.onLevelChange(level); 877 mGradientIsDirty = true; 878 mPathIsDirty = true; 879 invalidateSelf(); 880 return true; 881 } 882 883 /** 884 * This checks mGradientIsDirty, and if it is true, recomputes both our drawing 885 * rectangle (mRect) and the gradient itself, since it depends on our 886 * rectangle too. 887 * @return true if the resulting rectangle is not empty, false otherwise 888 */ 889 private boolean ensureValidRect() { 890 if (mGradientIsDirty) { 891 mGradientIsDirty = false; 892 893 Rect bounds = getBounds(); 894 float inset = 0; 895 896 if (mStrokePaint != null) { 897 inset = mStrokePaint.getStrokeWidth() * 0.5f; 898 } 899 900 final GradientState st = mGradientState; 901 902 mRect.set(bounds.left + inset, bounds.top + inset, 903 bounds.right - inset, bounds.bottom - inset); 904 905 final int[] gradientColors = st.mGradientColors; 906 if (gradientColors != null) { 907 final RectF r = mRect; 908 final float x0, x1, y0, y1; 909 910 if (st.mGradient == LINEAR_GRADIENT) { 911 final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; 912 switch (st.mOrientation) { 913 case TOP_BOTTOM: 914 x0 = r.left; y0 = r.top; 915 x1 = x0; y1 = level * r.bottom; 916 break; 917 case TR_BL: 918 x0 = r.right; y0 = r.top; 919 x1 = level * r.left; y1 = level * r.bottom; 920 break; 921 case RIGHT_LEFT: 922 x0 = r.right; y0 = r.top; 923 x1 = level * r.left; y1 = y0; 924 break; 925 case BR_TL: 926 x0 = r.right; y0 = r.bottom; 927 x1 = level * r.left; y1 = level * r.top; 928 break; 929 case BOTTOM_TOP: 930 x0 = r.left; y0 = r.bottom; 931 x1 = x0; y1 = level * r.top; 932 break; 933 case BL_TR: 934 x0 = r.left; y0 = r.bottom; 935 x1 = level * r.right; y1 = level * r.top; 936 break; 937 case LEFT_RIGHT: 938 x0 = r.left; y0 = r.top; 939 x1 = level * r.right; y1 = y0; 940 break; 941 default:/* TL_BR */ 942 x0 = r.left; y0 = r.top; 943 x1 = level * r.right; y1 = level * r.bottom; 944 break; 945 } 946 947 mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, 948 gradientColors, st.mPositions, Shader.TileMode.CLAMP)); 949 } else if (st.mGradient == RADIAL_GRADIENT) { 950 x0 = r.left + (r.right - r.left) * st.mCenterX; 951 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 952 953 float radius = st.mGradientRadius; 954 if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) { 955 // Fall back to parent width or height if intrinsic 956 // size is not specified. 957 final float width = st.mWidth >= 0 ? st.mWidth : r.width(); 958 final float height = st.mHeight >= 0 ? st.mHeight : r.height(); 959 radius *= Math.min(width, height); 960 } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) { 961 radius *= Math.min(r.width(), r.height()); 962 } 963 964 if (st.mUseLevel) { 965 radius *= getLevel() / 10000.0f; 966 } 967 968 mGradientRadius = radius; 969 970 if (radius <= 0) { 971 // We can't have a shader with non-positive radius, so 972 // let's have a very, very small radius. 973 radius = 0.001f; 974 } 975 976 mFillPaint.setShader(new RadialGradient( 977 x0, y0, radius, gradientColors, null, Shader.TileMode.CLAMP)); 978 } else if (st.mGradient == SWEEP_GRADIENT) { 979 x0 = r.left + (r.right - r.left) * st.mCenterX; 980 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 981 982 int[] tempColors = gradientColors; 983 float[] tempPositions = null; 984 985 if (st.mUseLevel) { 986 tempColors = st.mTempColors; 987 final int length = gradientColors.length; 988 if (tempColors == null || tempColors.length != length + 1) { 989 tempColors = st.mTempColors = new int[length + 1]; 990 } 991 System.arraycopy(gradientColors, 0, tempColors, 0, length); 992 tempColors[length] = gradientColors[length - 1]; 993 994 tempPositions = st.mTempPositions; 995 final float fraction = 1.0f / (length - 1); 996 if (tempPositions == null || tempPositions.length != length + 1) { 997 tempPositions = st.mTempPositions = new float[length + 1]; 998 } 999 1000 final float level = getLevel() / 10000.0f; 1001 for (int i = 0; i < length; i++) { 1002 tempPositions[i] = i * fraction * level; 1003 } 1004 tempPositions[length] = 1.0f; 1005 1006 } 1007 mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); 1008 } 1009 1010 // If we don't have a solid color, the alpha channel must be 1011 // maxed out so that alpha modulation works correctly. 1012 if (st.mSolidColors == null) { 1013 mFillPaint.setColor(Color.BLACK); 1014 } 1015 } 1016 } 1017 return !mRect.isEmpty(); 1018 } 1019 1020 @Override 1021 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 1022 throws XmlPullParserException, IOException { 1023 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable); 1024 super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible); 1025 updateStateFromTypedArray(a); 1026 a.recycle(); 1027 1028 inflateChildElements(r, parser, attrs, theme); 1029 1030 updateLocalState(r); 1031 } 1032 1033 @Override 1034 public void applyTheme(Theme t) { 1035 super.applyTheme(t); 1036 1037 final GradientState state = mGradientState; 1038 if (state == null) { 1039 return; 1040 } 1041 1042 if (state.mThemeAttrs != null) { 1043 final TypedArray a = t.resolveAttributes( 1044 state.mThemeAttrs, R.styleable.GradientDrawable); 1045 updateStateFromTypedArray(a); 1046 a.recycle(); 1047 } 1048 1049 if (state.mTint != null && state.mTint.canApplyTheme()) { 1050 state.mTint = state.mTint.obtainForTheme(t); 1051 } 1052 1053 if (state.mSolidColors != null && state.mSolidColors.canApplyTheme()) { 1054 state.mSolidColors = state.mSolidColors.obtainForTheme(t); 1055 } 1056 1057 if (state.mStrokeColors != null && state.mStrokeColors.canApplyTheme()) { 1058 state.mStrokeColors = state.mStrokeColors.obtainForTheme(t); 1059 } 1060 1061 applyThemeChildElements(t); 1062 1063 updateLocalState(t.getResources()); 1064 } 1065 1066 /** 1067 * Updates the constant state from the values in the typed array. 1068 */ 1069 private void updateStateFromTypedArray(TypedArray a) { 1070 final GradientState state = mGradientState; 1071 1072 // Account for any configuration changes. 1073 state.mChangingConfigurations |= a.getChangingConfigurations(); 1074 1075 // Extract the theme attributes, if any. 1076 state.mThemeAttrs = a.extractThemeAttrs(); 1077 1078 state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape); 1079 state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither); 1080 1081 if (state.mShape == RING) { 1082 state.mInnerRadius = a.getDimensionPixelSize( 1083 R.styleable.GradientDrawable_innerRadius, state.mInnerRadius); 1084 1085 if (state.mInnerRadius == -1) { 1086 state.mInnerRadiusRatio = a.getFloat( 1087 R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio); 1088 } 1089 1090 state.mThickness = a.getDimensionPixelSize( 1091 R.styleable.GradientDrawable_thickness, state.mThickness); 1092 1093 if (state.mThickness == -1) { 1094 state.mThicknessRatio = a.getFloat( 1095 R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio); 1096 } 1097 1098 state.mUseLevelForShape = a.getBoolean( 1099 R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); 1100 } 1101 1102 final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1); 1103 if (tintMode != -1) { 1104 state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN); 1105 } 1106 1107 final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint); 1108 if (tint != null) { 1109 state.mTint = tint; 1110 } 1111 1112 final int insetLeft = a.getDimensionPixelSize( 1113 R.styleable.GradientDrawable_opticalInsetLeft, state.mOpticalInsets.left); 1114 final int insetTop = a.getDimensionPixelSize( 1115 R.styleable.GradientDrawable_opticalInsetTop, state.mOpticalInsets.top); 1116 final int insetRight = a.getDimensionPixelSize( 1117 R.styleable.GradientDrawable_opticalInsetRight, state.mOpticalInsets.right); 1118 final int insetBottom = a.getDimensionPixelSize( 1119 R.styleable.GradientDrawable_opticalInsetBottom, state.mOpticalInsets.bottom); 1120 state.mOpticalInsets = Insets.of(insetLeft, insetTop, insetRight, insetBottom); 1121 } 1122 1123 @Override 1124 public boolean canApplyTheme() { 1125 return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme(); 1126 } 1127 1128 private void applyThemeChildElements(Theme t) { 1129 final GradientState st = mGradientState; 1130 1131 if (st.mAttrSize != null) { 1132 final TypedArray a = t.resolveAttributes( 1133 st.mAttrSize, R.styleable.GradientDrawableSize); 1134 updateGradientDrawableSize(a); 1135 a.recycle(); 1136 } 1137 1138 if (st.mAttrGradient != null) { 1139 final TypedArray a = t.resolveAttributes( 1140 st.mAttrGradient, R.styleable.GradientDrawableGradient); 1141 try { 1142 updateGradientDrawableGradient(t.getResources(), a); 1143 } catch (XmlPullParserException e) { 1144 throw new RuntimeException(e); 1145 } finally { 1146 a.recycle(); 1147 } 1148 } 1149 1150 if (st.mAttrSolid != null) { 1151 final TypedArray a = t.resolveAttributes( 1152 st.mAttrSolid, R.styleable.GradientDrawableSolid); 1153 updateGradientDrawableSolid(a); 1154 a.recycle(); 1155 } 1156 1157 if (st.mAttrStroke != null) { 1158 final TypedArray a = t.resolveAttributes( 1159 st.mAttrStroke, R.styleable.GradientDrawableStroke); 1160 updateGradientDrawableStroke(a); 1161 a.recycle(); 1162 } 1163 1164 if (st.mAttrCorners != null) { 1165 final TypedArray a = t.resolveAttributes( 1166 st.mAttrCorners, R.styleable.DrawableCorners); 1167 updateDrawableCorners(a); 1168 a.recycle(); 1169 } 1170 1171 if (st.mAttrPadding != null) { 1172 final TypedArray a = t.resolveAttributes( 1173 st.mAttrPadding, R.styleable.GradientDrawablePadding); 1174 updateGradientDrawablePadding(a); 1175 a.recycle(); 1176 } 1177 } 1178 1179 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 1180 Theme theme) throws XmlPullParserException, IOException { 1181 TypedArray a; 1182 int type; 1183 1184 final int innerDepth = parser.getDepth() + 1; 1185 int depth; 1186 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1187 && ((depth=parser.getDepth()) >= innerDepth 1188 || type != XmlPullParser.END_TAG)) { 1189 if (type != XmlPullParser.START_TAG) { 1190 continue; 1191 } 1192 1193 if (depth > innerDepth) { 1194 continue; 1195 } 1196 1197 String name = parser.getName(); 1198 1199 if (name.equals("size")) { 1200 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize); 1201 updateGradientDrawableSize(a); 1202 a.recycle(); 1203 } else if (name.equals("gradient")) { 1204 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient); 1205 updateGradientDrawableGradient(r, a); 1206 a.recycle(); 1207 } else if (name.equals("solid")) { 1208 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid); 1209 updateGradientDrawableSolid(a); 1210 a.recycle(); 1211 } else if (name.equals("stroke")) { 1212 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke); 1213 updateGradientDrawableStroke(a); 1214 a.recycle(); 1215 } else if (name.equals("corners")) { 1216 a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners); 1217 updateDrawableCorners(a); 1218 a.recycle(); 1219 } else if (name.equals("padding")) { 1220 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding); 1221 updateGradientDrawablePadding(a); 1222 a.recycle(); 1223 } else { 1224 Log.w("drawable", "Bad element under <shape>: " + name); 1225 } 1226 } 1227 } 1228 1229 private void updateGradientDrawablePadding(TypedArray a) { 1230 final GradientState st = mGradientState; 1231 1232 // Account for any configuration changes. 1233 st.mChangingConfigurations |= a.getChangingConfigurations(); 1234 1235 // Extract the theme attributes, if any. 1236 st.mAttrPadding = a.extractThemeAttrs(); 1237 1238 if (st.mPadding == null) { 1239 st.mPadding = new Rect(); 1240 } 1241 1242 final Rect pad = st.mPadding; 1243 pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left), 1244 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top), 1245 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right), 1246 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom)); 1247 mPadding = pad; 1248 } 1249 1250 private void updateDrawableCorners(TypedArray a) { 1251 final GradientState st = mGradientState; 1252 1253 // Account for any configuration changes. 1254 st.mChangingConfigurations |= a.getChangingConfigurations(); 1255 1256 // Extract the theme attributes, if any. 1257 st.mAttrCorners = a.extractThemeAttrs(); 1258 1259 final int radius = a.getDimensionPixelSize( 1260 R.styleable.DrawableCorners_radius, (int) st.mRadius); 1261 setCornerRadius(radius); 1262 1263 // TODO: Update these to be themeable. 1264 final int topLeftRadius = a.getDimensionPixelSize( 1265 R.styleable.DrawableCorners_topLeftRadius, radius); 1266 final int topRightRadius = a.getDimensionPixelSize( 1267 R.styleable.DrawableCorners_topRightRadius, radius); 1268 final int bottomLeftRadius = a.getDimensionPixelSize( 1269 R.styleable.DrawableCorners_bottomLeftRadius, radius); 1270 final int bottomRightRadius = a.getDimensionPixelSize( 1271 R.styleable.DrawableCorners_bottomRightRadius, radius); 1272 if (topLeftRadius != radius || topRightRadius != radius || 1273 bottomLeftRadius != radius || bottomRightRadius != radius) { 1274 // The corner radii are specified in clockwise order (see Path.addRoundRect()) 1275 setCornerRadii(new float[] { 1276 topLeftRadius, topLeftRadius, 1277 topRightRadius, topRightRadius, 1278 bottomRightRadius, bottomRightRadius, 1279 bottomLeftRadius, bottomLeftRadius 1280 }); 1281 } 1282 } 1283 1284 private void updateGradientDrawableStroke(TypedArray a) { 1285 final GradientState st = mGradientState; 1286 1287 // Account for any configuration changes. 1288 st.mChangingConfigurations |= a.getChangingConfigurations(); 1289 1290 // Extract the theme attributes, if any. 1291 st.mAttrStroke = a.extractThemeAttrs(); 1292 1293 // We have an explicit stroke defined, so the default stroke width 1294 // must be at least 0 or the current stroke width. 1295 final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth); 1296 final int width = a.getDimensionPixelSize( 1297 R.styleable.GradientDrawableStroke_width, defaultStrokeWidth); 1298 final float dashWidth = a.getDimension( 1299 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth); 1300 1301 ColorStateList colorStateList = a.getColorStateList( 1302 R.styleable.GradientDrawableStroke_color); 1303 if (colorStateList == null) { 1304 colorStateList = st.mStrokeColors; 1305 } 1306 1307 if (dashWidth != 0.0f) { 1308 final float dashGap = a.getDimension( 1309 R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap); 1310 setStroke(width, colorStateList, dashWidth, dashGap); 1311 } else { 1312 setStroke(width, colorStateList); 1313 } 1314 } 1315 1316 private void updateGradientDrawableSolid(TypedArray a) { 1317 final GradientState st = mGradientState; 1318 1319 // Account for any configuration changes. 1320 st.mChangingConfigurations |= a.getChangingConfigurations(); 1321 1322 // Extract the theme attributes, if any. 1323 st.mAttrSolid = a.extractThemeAttrs(); 1324 1325 final ColorStateList colorStateList = a.getColorStateList( 1326 R.styleable.GradientDrawableSolid_color); 1327 if (colorStateList != null) { 1328 setColor(colorStateList); 1329 } 1330 } 1331 1332 private void updateGradientDrawableGradient(Resources r, TypedArray a) 1333 throws XmlPullParserException { 1334 final GradientState st = mGradientState; 1335 1336 // Account for any configuration changes. 1337 st.mChangingConfigurations |= a.getChangingConfigurations(); 1338 1339 // Extract the theme attributes, if any. 1340 st.mAttrGradient = a.extractThemeAttrs(); 1341 1342 st.mCenterX = getFloatOrFraction( 1343 a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX); 1344 st.mCenterY = getFloatOrFraction( 1345 a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY); 1346 st.mUseLevel = a.getBoolean( 1347 R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel); 1348 st.mGradient = a.getInt( 1349 R.styleable.GradientDrawableGradient_type, st.mGradient); 1350 1351 // TODO: Update these to be themeable. 1352 final int startColor = a.getColor( 1353 R.styleable.GradientDrawableGradient_startColor, 0); 1354 final boolean hasCenterColor = a.hasValue( 1355 R.styleable.GradientDrawableGradient_centerColor); 1356 final int centerColor = a.getColor( 1357 R.styleable.GradientDrawableGradient_centerColor, 0); 1358 final int endColor = a.getColor( 1359 R.styleable.GradientDrawableGradient_endColor, 0); 1360 1361 if (hasCenterColor) { 1362 st.mGradientColors = new int[3]; 1363 st.mGradientColors[0] = startColor; 1364 st.mGradientColors[1] = centerColor; 1365 st.mGradientColors[2] = endColor; 1366 1367 st.mPositions = new float[3]; 1368 st.mPositions[0] = 0.0f; 1369 // Since 0.5f is default value, try to take the one that isn't 0.5f 1370 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; 1371 st.mPositions[2] = 1f; 1372 } else { 1373 st.mGradientColors = new int[2]; 1374 st.mGradientColors[0] = startColor; 1375 st.mGradientColors[1] = endColor; 1376 } 1377 1378 if (st.mGradient == LINEAR_GRADIENT) { 1379 int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); 1380 angle %= 360; 1381 1382 if (angle % 45 != 0) { 1383 throw new XmlPullParserException(a.getPositionDescription() 1384 + "<gradient> tag requires 'angle' attribute to " 1385 + "be a multiple of 45"); 1386 } 1387 1388 st.mAngle = angle; 1389 1390 switch (angle) { 1391 case 0: 1392 st.mOrientation = Orientation.LEFT_RIGHT; 1393 break; 1394 case 45: 1395 st.mOrientation = Orientation.BL_TR; 1396 break; 1397 case 90: 1398 st.mOrientation = Orientation.BOTTOM_TOP; 1399 break; 1400 case 135: 1401 st.mOrientation = Orientation.BR_TL; 1402 break; 1403 case 180: 1404 st.mOrientation = Orientation.RIGHT_LEFT; 1405 break; 1406 case 225: 1407 st.mOrientation = Orientation.TR_BL; 1408 break; 1409 case 270: 1410 st.mOrientation = Orientation.TOP_BOTTOM; 1411 break; 1412 case 315: 1413 st.mOrientation = Orientation.TL_BR; 1414 break; 1415 } 1416 } else { 1417 final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); 1418 if (tv != null) { 1419 final float radius; 1420 final int radiusType; 1421 if (tv.type == TypedValue.TYPE_FRACTION) { 1422 radius = tv.getFraction(1.0f, 1.0f); 1423 1424 final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) 1425 & TypedValue.COMPLEX_UNIT_MASK; 1426 if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { 1427 radiusType = RADIUS_TYPE_FRACTION_PARENT; 1428 } else { 1429 radiusType = RADIUS_TYPE_FRACTION; 1430 } 1431 } else if (tv.type == TypedValue.TYPE_DIMENSION) { 1432 radius = tv.getDimension(r.getDisplayMetrics()); 1433 radiusType = RADIUS_TYPE_PIXELS; 1434 } else { 1435 radius = tv.getFloat(); 1436 radiusType = RADIUS_TYPE_PIXELS; 1437 } 1438 1439 st.mGradientRadius = radius; 1440 st.mGradientRadiusType = radiusType; 1441 } else if (st.mGradient == RADIAL_GRADIENT) { 1442 throw new XmlPullParserException( 1443 a.getPositionDescription() 1444 + "<gradient> tag requires 'gradientRadius' " 1445 + "attribute with radial type"); 1446 } 1447 } 1448 } 1449 1450 private void updateGradientDrawableSize(TypedArray a) { 1451 final GradientState st = mGradientState; 1452 1453 // Account for any configuration changes. 1454 st.mChangingConfigurations |= a.getChangingConfigurations(); 1455 1456 // Extract the theme attributes, if any. 1457 st.mAttrSize = a.extractThemeAttrs(); 1458 1459 st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth); 1460 st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight); 1461 } 1462 1463 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { 1464 TypedValue tv = a.peekValue(index); 1465 float v = defaultValue; 1466 if (tv != null) { 1467 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; 1468 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 1469 } 1470 return v; 1471 } 1472 1473 @Override 1474 public int getIntrinsicWidth() { 1475 return mGradientState.mWidth; 1476 } 1477 1478 @Override 1479 public int getIntrinsicHeight() { 1480 return mGradientState.mHeight; 1481 } 1482 1483 /** @hide */ 1484 @Override 1485 public Insets getOpticalInsets() { 1486 return mGradientState.mOpticalInsets; 1487 } 1488 1489 @Override 1490 public ConstantState getConstantState() { 1491 mGradientState.mChangingConfigurations = getChangingConfigurations(); 1492 return mGradientState; 1493 } 1494 1495 private boolean isOpaqueForState() { 1496 if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null 1497 && !isOpaque(mStrokePaint.getColor())) { 1498 return false; 1499 } 1500 1501 if (!isOpaque(mFillPaint.getColor())) { 1502 return false; 1503 } 1504 1505 return true; 1506 } 1507 1508 @Override 1509 public void getOutline(Outline outline) { 1510 final GradientState st = mGradientState; 1511 final Rect bounds = getBounds(); 1512 // only report non-zero alpha if shape being drawn is opaque 1513 outline.setAlpha(st.mOpaqueOverShape && isOpaqueForState() ? (mAlpha / 255.0f) : 0.0f); 1514 1515 switch (st.mShape) { 1516 case RECTANGLE: 1517 if (st.mRadiusArray != null) { 1518 buildPathIfDirty(); 1519 outline.setConvexPath(mPath); 1520 return; 1521 } 1522 1523 float rad = 0; 1524 if (st.mRadius > 0.0f) { 1525 // clamp the radius based on width & height, matching behavior in draw() 1526 rad = Math.min(st.mRadius, 1527 Math.min(bounds.width(), bounds.height()) * 0.5f); 1528 } 1529 outline.setRoundRect(bounds, rad); 1530 return; 1531 case OVAL: 1532 outline.setOval(bounds); 1533 return; 1534 case LINE: 1535 // Hairlines (0-width stroke) must have a non-empty outline for 1536 // shadows to draw correctly, so we'll use a very small width. 1537 final float halfStrokeWidth = mStrokePaint == null ? 1538 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f; 1539 final float centerY = bounds.centerY(); 1540 final int top = (int) Math.floor(centerY - halfStrokeWidth); 1541 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth); 1542 1543 outline.setRect(bounds.left, top, bounds.right, bottom); 1544 return; 1545 default: 1546 // TODO: support more complex shapes 1547 } 1548 } 1549 1550 @Override 1551 public Drawable mutate() { 1552 if (!mMutated && super.mutate() == this) { 1553 mGradientState = new GradientState(mGradientState); 1554 updateLocalState(null); 1555 mMutated = true; 1556 } 1557 return this; 1558 } 1559 1560 /** 1561 * @hide 1562 */ 1563 public void clearMutated() { 1564 super.clearMutated(); 1565 mMutated = false; 1566 } 1567 1568 final static class GradientState extends ConstantState { 1569 public int mChangingConfigurations; 1570 public int mShape = RECTANGLE; 1571 public int mGradient = LINEAR_GRADIENT; 1572 public int mAngle = 0; 1573 public Orientation mOrientation; 1574 public ColorStateList mSolidColors; 1575 public ColorStateList mStrokeColors; 1576 public int[] mGradientColors; 1577 public int[] mTempColors; // no need to copy 1578 public float[] mTempPositions; // no need to copy 1579 public float[] mPositions; 1580 public int mStrokeWidth = -1; // if >= 0 use stroking. 1581 public float mStrokeDashWidth = 0.0f; 1582 public float mStrokeDashGap = 0.0f; 1583 public float mRadius = 0.0f; // use this if mRadiusArray is null 1584 public float[] mRadiusArray = null; 1585 public Rect mPadding = null; 1586 public int mWidth = -1; 1587 public int mHeight = -1; 1588 public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; 1589 public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; 1590 public int mInnerRadius = -1; 1591 public int mThickness = -1; 1592 public boolean mDither = false; 1593 public Insets mOpticalInsets = Insets.NONE; 1594 1595 float mCenterX = 0.5f; 1596 float mCenterY = 0.5f; 1597 float mGradientRadius = 0.5f; 1598 int mGradientRadiusType = RADIUS_TYPE_PIXELS; 1599 boolean mUseLevel = false; 1600 boolean mUseLevelForShape = true; 1601 1602 boolean mOpaqueOverBounds; 1603 boolean mOpaqueOverShape; 1604 1605 ColorStateList mTint = null; 1606 PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 1607 1608 int[] mThemeAttrs; 1609 int[] mAttrSize; 1610 int[] mAttrGradient; 1611 int[] mAttrSolid; 1612 int[] mAttrStroke; 1613 int[] mAttrCorners; 1614 int[] mAttrPadding; 1615 1616 public GradientState(Orientation orientation, int[] gradientColors) { 1617 mOrientation = orientation; 1618 setGradientColors(gradientColors); 1619 } 1620 1621 public GradientState(GradientState state) { 1622 mChangingConfigurations = state.mChangingConfigurations; 1623 mShape = state.mShape; 1624 mGradient = state.mGradient; 1625 mAngle = state.mAngle; 1626 mOrientation = state.mOrientation; 1627 mSolidColors = state.mSolidColors; 1628 if (state.mGradientColors != null) { 1629 mGradientColors = state.mGradientColors.clone(); 1630 } 1631 if (state.mPositions != null) { 1632 mPositions = state.mPositions.clone(); 1633 } 1634 mStrokeColors = state.mStrokeColors; 1635 mStrokeWidth = state.mStrokeWidth; 1636 mStrokeDashWidth = state.mStrokeDashWidth; 1637 mStrokeDashGap = state.mStrokeDashGap; 1638 mRadius = state.mRadius; 1639 if (state.mRadiusArray != null) { 1640 mRadiusArray = state.mRadiusArray.clone(); 1641 } 1642 if (state.mPadding != null) { 1643 mPadding = new Rect(state.mPadding); 1644 } 1645 mWidth = state.mWidth; 1646 mHeight = state.mHeight; 1647 mInnerRadiusRatio = state.mInnerRadiusRatio; 1648 mThicknessRatio = state.mThicknessRatio; 1649 mInnerRadius = state.mInnerRadius; 1650 mThickness = state.mThickness; 1651 mDither = state.mDither; 1652 mOpticalInsets = state.mOpticalInsets; 1653 mCenterX = state.mCenterX; 1654 mCenterY = state.mCenterY; 1655 mGradientRadius = state.mGradientRadius; 1656 mGradientRadiusType = state.mGradientRadiusType; 1657 mUseLevel = state.mUseLevel; 1658 mUseLevelForShape = state.mUseLevelForShape; 1659 mOpaqueOverBounds = state.mOpaqueOverBounds; 1660 mOpaqueOverShape = state.mOpaqueOverShape; 1661 mTint = state.mTint; 1662 mTintMode = state.mTintMode; 1663 mThemeAttrs = state.mThemeAttrs; 1664 mAttrSize = state.mAttrSize; 1665 mAttrGradient = state.mAttrGradient; 1666 mAttrSolid = state.mAttrSolid; 1667 mAttrStroke = state.mAttrStroke; 1668 mAttrCorners = state.mAttrCorners; 1669 mAttrPadding = state.mAttrPadding; 1670 } 1671 1672 @Override 1673 public boolean canApplyTheme() { 1674 return mThemeAttrs != null 1675 || mAttrSize != null || mAttrGradient != null 1676 || mAttrSolid != null || mAttrStroke != null 1677 || mAttrCorners != null || mAttrPadding != null 1678 || (mTint != null && mTint.canApplyTheme()) 1679 || (mStrokeColors != null && mStrokeColors.canApplyTheme()) 1680 || (mSolidColors != null && mSolidColors.canApplyTheme()) 1681 || super.canApplyTheme(); 1682 } 1683 1684 @Override 1685 public Drawable newDrawable() { 1686 return new GradientDrawable(this, null); 1687 } 1688 1689 @Override 1690 public Drawable newDrawable(Resources res) { 1691 return new GradientDrawable(this, res); 1692 } 1693 1694 @Override 1695 public int getChangingConfigurations() { 1696 return mChangingConfigurations 1697 | (mStrokeColors != null ? mStrokeColors.getChangingConfigurations() : 0) 1698 | (mSolidColors != null ? mSolidColors.getChangingConfigurations() : 0) 1699 | (mTint != null ? mTint.getChangingConfigurations() : 0); 1700 } 1701 1702 public void setShape(int shape) { 1703 mShape = shape; 1704 computeOpacity(); 1705 } 1706 1707 public void setGradientType(int gradient) { 1708 mGradient = gradient; 1709 } 1710 1711 public void setGradientCenter(float x, float y) { 1712 mCenterX = x; 1713 mCenterY = y; 1714 } 1715 1716 public void setGradientColors(int[] colors) { 1717 mGradientColors = colors; 1718 mSolidColors = null; 1719 computeOpacity(); 1720 } 1721 1722 public void setSolidColors(ColorStateList colors) { 1723 mGradientColors = null; 1724 mSolidColors = colors; 1725 computeOpacity(); 1726 } 1727 1728 private void computeOpacity() { 1729 mOpaqueOverBounds = false; 1730 mOpaqueOverShape = false; 1731 1732 if (mGradientColors != null) { 1733 for (int i = 0; i < mGradientColors.length; i++) { 1734 if (!isOpaque(mGradientColors[i])) { 1735 return; 1736 } 1737 } 1738 } 1739 1740 // An unfilled shape is not opaque over bounds or shape 1741 if (mGradientColors == null && mSolidColors == null) { 1742 return; 1743 } 1744 1745 // Colors are opaque, so opaqueOverShape=true, 1746 mOpaqueOverShape = true; 1747 // and opaqueOverBounds=true if shape fills bounds 1748 mOpaqueOverBounds = mShape == RECTANGLE 1749 && mRadius <= 0 1750 && mRadiusArray == null; 1751 } 1752 1753 public void setStroke(int width, ColorStateList colors, float dashWidth, float dashGap) { 1754 mStrokeWidth = width; 1755 mStrokeColors = colors; 1756 mStrokeDashWidth = dashWidth; 1757 mStrokeDashGap = dashGap; 1758 computeOpacity(); 1759 } 1760 1761 public void setCornerRadius(float radius) { 1762 if (radius < 0) { 1763 radius = 0; 1764 } 1765 mRadius = radius; 1766 mRadiusArray = null; 1767 } 1768 1769 public void setCornerRadii(float[] radii) { 1770 mRadiusArray = radii; 1771 if (radii == null) { 1772 mRadius = 0; 1773 } 1774 } 1775 1776 public void setSize(int width, int height) { 1777 mWidth = width; 1778 mHeight = height; 1779 } 1780 1781 public void setGradientRadius(float gradientRadius, int type) { 1782 mGradientRadius = gradientRadius; 1783 mGradientRadiusType = type; 1784 } 1785 } 1786 1787 static boolean isOpaque(int color) { 1788 return ((color >> 24) & 0xff) == 0xff; 1789 } 1790 1791 /** 1792 * Creates a new themed GradientDrawable based on the specified constant state. 1793 * <p> 1794 * The resulting drawable is guaranteed to have a new constant state. 1795 * 1796 * @param state Constant state from which the drawable inherits 1797 */ 1798 private GradientDrawable(GradientState state, Resources res) { 1799 mGradientState = state; 1800 1801 updateLocalState(res); 1802 } 1803 1804 private void updateLocalState(Resources res) { 1805 final GradientState state = mGradientState; 1806 1807 if (state.mSolidColors != null) { 1808 final int[] currentState = getState(); 1809 final int stateColor = state.mSolidColors.getColorForState(currentState, 0); 1810 mFillPaint.setColor(stateColor); 1811 } else if (state.mGradientColors == null) { 1812 // If we don't have a solid color and we don't have a gradient, 1813 // the app is stroking the shape, set the color to the default 1814 // value of state.mSolidColor 1815 mFillPaint.setColor(0); 1816 } else { 1817 // Otherwise, make sure the fill alpha is maxed out. 1818 mFillPaint.setColor(Color.BLACK); 1819 } 1820 1821 mPadding = state.mPadding; 1822 1823 if (state.mStrokeWidth >= 0) { 1824 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1825 mStrokePaint.setStyle(Paint.Style.STROKE); 1826 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1827 1828 if (state.mStrokeColors != null) { 1829 final int[] currentState = getState(); 1830 final int strokeStateColor = state.mStrokeColors.getColorForState( 1831 currentState, 0); 1832 mStrokePaint.setColor(strokeStateColor); 1833 } 1834 1835 if (state.mStrokeDashWidth != 0.0f) { 1836 final DashPathEffect e = new DashPathEffect( 1837 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1838 mStrokePaint.setPathEffect(e); 1839 } 1840 } 1841 1842 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 1843 mGradientIsDirty = true; 1844 1845 state.computeOpacity(); 1846 } 1847} 1848