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