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