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