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