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