GradientDrawable.java revision a72f59a858ebda3d96583502d8ac02443d6223c3
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 colors will affect all instances 496 * of a drawable loaded from a resource. It is recommended to invoke 497 * {@link #mutate()} before changing the colors.</p> 498 * 499 * @param colors 2 or more ARGB colors 500 * 501 * @see #mutate() 502 * @see #setColor(int) 503 */ 504 public void setColors(int[] colors) { 505 mGradientState.setColors(colors); 506 mGradientIsDirty = true; 507 invalidateSelf(); 508 } 509 510 @Override 511 public void draw(Canvas canvas) { 512 if (!ensureValidRect()) { 513 // nothing to draw 514 return; 515 } 516 517 // remember the alpha values, in case we temporarily overwrite them 518 // when we modulate them with mAlpha 519 final int prevFillAlpha = mFillPaint.getAlpha(); 520 final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0; 521 // compute the modulate alpha values 522 final int currFillAlpha = modulateAlpha(prevFillAlpha); 523 final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha); 524 525 final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null && 526 mStrokePaint.getStrokeWidth() > 0; 527 final boolean haveFill = currFillAlpha > 0; 528 final GradientState st = mGradientState; 529 final ColorFilter colorFilter = mColorFilter != null ? mColorFilter : mTintFilter; 530 531 /* we need a layer iff we're drawing both a fill and stroke, and the 532 stroke is non-opaque, and our shapetype actually supports 533 fill+stroke. Otherwise we can just draw the stroke (if any) on top 534 of the fill (if any) without worrying about blending artifacts. 535 */ 536 final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && 537 currStrokeAlpha < 255 && (mAlpha < 255 || colorFilter != null); 538 539 /* Drawing with a layer is slower than direct drawing, but it 540 allows us to apply paint effects like alpha and colorfilter to 541 the result of multiple separate draws. In our case, if the user 542 asks for a non-opaque alpha value (via setAlpha), and we're 543 stroking, then we need to apply the alpha AFTER we've drawn 544 both the fill and the stroke. 545 */ 546 if (useLayer) { 547 if (mLayerPaint == null) { 548 mLayerPaint = new Paint(); 549 } 550 mLayerPaint.setDither(st.mDither); 551 mLayerPaint.setAlpha(mAlpha); 552 mLayerPaint.setColorFilter(colorFilter); 553 554 float rad = mStrokePaint.getStrokeWidth(); 555 canvas.saveLayer(mRect.left - rad, mRect.top - rad, 556 mRect.right + rad, mRect.bottom + rad, 557 mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); 558 559 // don't perform the filter in our individual paints 560 // since the layer will do it for us 561 mFillPaint.setColorFilter(null); 562 mStrokePaint.setColorFilter(null); 563 } else { 564 /* if we're not using a layer, apply the dither/filter to our 565 individual paints 566 */ 567 mFillPaint.setAlpha(currFillAlpha); 568 mFillPaint.setDither(st.mDither); 569 mFillPaint.setColorFilter(colorFilter); 570 if (colorFilter != null && st.mColorStateList == null) { 571 mFillPaint.setColor(mAlpha << 24); 572 } 573 if (haveStroke) { 574 mStrokePaint.setAlpha(currStrokeAlpha); 575 mStrokePaint.setDither(st.mDither); 576 mStrokePaint.setColorFilter(colorFilter); 577 } 578 } 579 580 switch (st.mShape) { 581 case RECTANGLE: 582 if (st.mRadiusArray != null) { 583 buildPathIfDirty(); 584 canvas.drawPath(mPath, mFillPaint); 585 if (haveStroke) { 586 canvas.drawPath(mPath, mStrokePaint); 587 } 588 } else if (st.mRadius > 0.0f) { 589 // since the caller is only giving us 1 value, we will force 590 // it to be square if the rect is too small in one dimension 591 // to show it. If we did nothing, Skia would clamp the rad 592 // independently along each axis, giving us a thin ellipse 593 // if the rect were very wide but not very tall 594 float rad = Math.min(st.mRadius, 595 Math.min(mRect.width(), mRect.height()) * 0.5f); 596 canvas.drawRoundRect(mRect, rad, rad, mFillPaint); 597 if (haveStroke) { 598 canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); 599 } 600 } else { 601 if (mFillPaint.getColor() != 0 || colorFilter != null || 602 mFillPaint.getShader() != null) { 603 canvas.drawRect(mRect, mFillPaint); 604 } 605 if (haveStroke) { 606 canvas.drawRect(mRect, mStrokePaint); 607 } 608 } 609 break; 610 case OVAL: 611 canvas.drawOval(mRect, mFillPaint); 612 if (haveStroke) { 613 canvas.drawOval(mRect, mStrokePaint); 614 } 615 break; 616 case LINE: { 617 RectF r = mRect; 618 float y = r.centerY(); 619 if (haveStroke) { 620 canvas.drawLine(r.left, y, r.right, y, mStrokePaint); 621 } 622 break; 623 } 624 case RING: 625 Path path = buildRing(st); 626 canvas.drawPath(path, mFillPaint); 627 if (haveStroke) { 628 canvas.drawPath(path, mStrokePaint); 629 } 630 break; 631 } 632 633 if (useLayer) { 634 canvas.restore(); 635 } else { 636 mFillPaint.setAlpha(prevFillAlpha); 637 if (haveStroke) { 638 mStrokePaint.setAlpha(prevStrokeAlpha); 639 } 640 } 641 } 642 643 private void buildPathIfDirty() { 644 final GradientState st = mGradientState; 645 if (mPathIsDirty) { 646 ensureValidRect(); 647 mPath.reset(); 648 mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); 649 mPathIsDirty = false; 650 } 651 } 652 653 private Path buildRing(GradientState st) { 654 if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath; 655 mPathIsDirty = false; 656 657 float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f; 658 659 RectF bounds = new RectF(mRect); 660 661 float x = bounds.width() / 2.0f; 662 float y = bounds.height() / 2.0f; 663 664 float thickness = st.mThickness != -1 ? 665 st.mThickness : bounds.width() / st.mThicknessRatio; 666 // inner radius 667 float radius = st.mInnerRadius != -1 ? 668 st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio; 669 670 RectF innerBounds = new RectF(bounds); 671 innerBounds.inset(x - radius, y - radius); 672 673 bounds = new RectF(innerBounds); 674 bounds.inset(-thickness, -thickness); 675 676 if (mRingPath == null) { 677 mRingPath = new Path(); 678 } else { 679 mRingPath.reset(); 680 } 681 682 final Path ringPath = mRingPath; 683 // arcTo treats the sweep angle mod 360, so check for that, since we 684 // think 360 means draw the entire oval 685 if (sweep < 360 && sweep > -360) { 686 ringPath.setFillType(Path.FillType.EVEN_ODD); 687 // inner top 688 ringPath.moveTo(x + radius, y); 689 // outer top 690 ringPath.lineTo(x + radius + thickness, y); 691 // outer arc 692 ringPath.arcTo(bounds, 0.0f, sweep, false); 693 // inner arc 694 ringPath.arcTo(innerBounds, sweep, -sweep, false); 695 ringPath.close(); 696 } else { 697 // add the entire ovals 698 ringPath.addOval(bounds, Path.Direction.CW); 699 ringPath.addOval(innerBounds, Path.Direction.CCW); 700 } 701 702 return ringPath; 703 } 704 705 /** 706 * <p>Changes this drawable to use a single color instead of a gradient.</p> 707 * <p><strong>Note</strong>: changing color will affect all instances 708 * of a drawable loaded from a resource. It is recommended to invoke 709 * {@link #mutate()} before changing the color.</p> 710 * 711 * @param argb The color used to fill the shape 712 * 713 * @see #mutate() 714 * @see #setColors(int[]) 715 */ 716 public void setColor(int argb) { 717 mGradientState.setColorStateList(ColorStateList.valueOf(argb)); 718 mFillPaint.setColor(argb); 719 invalidateSelf(); 720 } 721 722 /** 723 * Changes this drawable to use a single color state list instead of a 724 * gradient. Calling this method with a null argument will clear the color 725 * and is equivalent to calling {@link #setColor(int)} with the argument 726 * {@link Color#TRANSPARENT}. 727 * <p> 728 * <strong>Note</strong>: changing color will affect all instances of a 729 * drawable loaded from a resource. It is recommended to invoke 730 * {@link #mutate()} before changing the color.</p> 731 * 732 * @param colorStateList The color state list used to fill the shape 733 * @see #mutate() 734 */ 735 public void setColor(ColorStateList colorStateList) { 736 mGradientState.setColorStateList(colorStateList); 737 final int color; 738 if (colorStateList == null) { 739 color = Color.TRANSPARENT; 740 } else { 741 final int[] stateSet = getState(); 742 color = colorStateList.getColorForState(stateSet, 0); 743 } 744 mFillPaint.setColor(color); 745 invalidateSelf(); 746 } 747 748 @Override 749 protected boolean onStateChange(int[] stateSet) { 750 boolean invalidateSelf = false; 751 752 final GradientState s = mGradientState; 753 final ColorStateList stateList = s.mColorStateList; 754 if (stateList != null) { 755 final int newColor = stateList.getColorForState(stateSet, 0); 756 final int oldColor = mFillPaint.getColor(); 757 if (oldColor != newColor) { 758 mFillPaint.setColor(newColor); 759 invalidateSelf = true; 760 } 761 } 762 763 final Paint strokePaint = mStrokePaint; 764 if (strokePaint != null) { 765 final ColorStateList strokeStateList = s.mStrokeColorStateList; 766 if (strokeStateList != null) { 767 final int newStrokeColor = strokeStateList.getColorForState(stateSet, 0); 768 final int oldStrokeColor = strokePaint.getColor(); 769 if (oldStrokeColor != newStrokeColor) { 770 strokePaint.setColor(newStrokeColor); 771 invalidateSelf = true; 772 } 773 } 774 } 775 776 if (s.mTint != null && s.mTintMode != null) { 777 mTintFilter = updateTintFilter(mTintFilter, s.mTint, s.mTintMode); 778 invalidateSelf = true; 779 } 780 781 if (invalidateSelf) { 782 invalidateSelf(); 783 return true; 784 } 785 786 return false; 787 } 788 789 @Override 790 public boolean isStateful() { 791 final GradientState s = mGradientState; 792 return super.isStateful() 793 || (s.mColorStateList != null && s.mColorStateList.isStateful()) 794 || (s.mStrokeColorStateList != null && s.mStrokeColorStateList.isStateful()) 795 || (s.mTint != null && s.mTint.isStateful()); 796 } 797 798 @Override 799 public int getChangingConfigurations() { 800 return super.getChangingConfigurations() | mGradientState.mChangingConfigurations; 801 } 802 803 @Override 804 public void setAlpha(int alpha) { 805 if (alpha != mAlpha) { 806 mAlpha = alpha; 807 invalidateSelf(); 808 } 809 } 810 811 @Override 812 public int getAlpha() { 813 return mAlpha; 814 } 815 816 @Override 817 public void setDither(boolean dither) { 818 if (dither != mGradientState.mDither) { 819 mGradientState.mDither = dither; 820 invalidateSelf(); 821 } 822 } 823 824 @Override 825 public ColorFilter getColorFilter() { 826 return mColorFilter; 827 } 828 829 @Override 830 public void setColorFilter(ColorFilter cf) { 831 if (cf != mColorFilter) { 832 mColorFilter = cf; 833 invalidateSelf(); 834 } 835 } 836 837 @Override 838 public void setTintList(ColorStateList tint) { 839 mGradientState.mTint = tint; 840 mTintFilter = updateTintFilter(mTintFilter, tint, mGradientState.mTintMode); 841 invalidateSelf(); 842 } 843 844 @Override 845 public void setTintMode(PorterDuff.Mode tintMode) { 846 mGradientState.mTintMode = tintMode; 847 mTintFilter = updateTintFilter(mTintFilter, mGradientState.mTint, tintMode); 848 invalidateSelf(); 849 } 850 851 @Override 852 public int getOpacity() { 853 return (mAlpha == 255 && mGradientState.mOpaqueOverBounds && isOpaqueForState()) ? 854 PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT; 855 } 856 857 @Override 858 protected void onBoundsChange(Rect r) { 859 super.onBoundsChange(r); 860 mRingPath = null; 861 mPathIsDirty = true; 862 mGradientIsDirty = true; 863 } 864 865 @Override 866 protected boolean onLevelChange(int level) { 867 super.onLevelChange(level); 868 mGradientIsDirty = true; 869 mPathIsDirty = true; 870 invalidateSelf(); 871 return true; 872 } 873 874 /** 875 * This checks mGradientIsDirty, and if it is true, recomputes both our drawing 876 * rectangle (mRect) and the gradient itself, since it depends on our 877 * rectangle too. 878 * @return true if the resulting rectangle is not empty, false otherwise 879 */ 880 private boolean ensureValidRect() { 881 if (mGradientIsDirty) { 882 mGradientIsDirty = false; 883 884 Rect bounds = getBounds(); 885 float inset = 0; 886 887 if (mStrokePaint != null) { 888 inset = mStrokePaint.getStrokeWidth() * 0.5f; 889 } 890 891 final GradientState st = mGradientState; 892 893 mRect.set(bounds.left + inset, bounds.top + inset, 894 bounds.right - inset, bounds.bottom - inset); 895 896 final int[] colors = st.mColors; 897 if (colors != null) { 898 RectF r = mRect; 899 float x0, x1, y0, y1; 900 901 if (st.mGradient == LINEAR_GRADIENT) { 902 final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; 903 switch (st.mOrientation) { 904 case TOP_BOTTOM: 905 x0 = r.left; y0 = r.top; 906 x1 = x0; y1 = level * r.bottom; 907 break; 908 case TR_BL: 909 x0 = r.right; y0 = r.top; 910 x1 = level * r.left; y1 = level * r.bottom; 911 break; 912 case RIGHT_LEFT: 913 x0 = r.right; y0 = r.top; 914 x1 = level * r.left; y1 = y0; 915 break; 916 case BR_TL: 917 x0 = r.right; y0 = r.bottom; 918 x1 = level * r.left; y1 = level * r.top; 919 break; 920 case BOTTOM_TOP: 921 x0 = r.left; y0 = r.bottom; 922 x1 = x0; y1 = level * r.top; 923 break; 924 case BL_TR: 925 x0 = r.left; y0 = r.bottom; 926 x1 = level * r.right; y1 = level * r.top; 927 break; 928 case LEFT_RIGHT: 929 x0 = r.left; y0 = r.top; 930 x1 = level * r.right; y1 = y0; 931 break; 932 default:/* TL_BR */ 933 x0 = r.left; y0 = r.top; 934 x1 = level * r.right; y1 = level * r.bottom; 935 break; 936 } 937 938 mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, 939 colors, st.mPositions, Shader.TileMode.CLAMP)); 940 } else if (st.mGradient == RADIAL_GRADIENT) { 941 x0 = r.left + (r.right - r.left) * st.mCenterX; 942 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 943 944 float radius = st.mGradientRadius; 945 if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION) { 946 // Fall back to parent width or height if intrinsic 947 // size is not specified. 948 final float width = st.mWidth >= 0 ? st.mWidth : r.width(); 949 final float height = st.mHeight >= 0 ? st.mHeight : r.height(); 950 radius *= Math.min(width, height); 951 } else if (st.mGradientRadiusType == RADIUS_TYPE_FRACTION_PARENT) { 952 radius *= Math.min(r.width(), r.height()); 953 } 954 955 if (st.mUseLevel) { 956 radius *= getLevel() / 10000.0f; 957 } 958 959 mGradientRadius = radius; 960 961 if (radius <= 0) { 962 // We can't have a shader with non-positive radius, so 963 // let's have a very, very small radius. 964 radius = 0.001f; 965 } 966 967 mFillPaint.setShader(new RadialGradient( 968 x0, y0, radius, colors, null, Shader.TileMode.CLAMP)); 969 } else if (st.mGradient == SWEEP_GRADIENT) { 970 x0 = r.left + (r.right - r.left) * st.mCenterX; 971 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 972 973 int[] tempColors = colors; 974 float[] tempPositions = null; 975 976 if (st.mUseLevel) { 977 tempColors = st.mTempColors; 978 final int length = colors.length; 979 if (tempColors == null || tempColors.length != length + 1) { 980 tempColors = st.mTempColors = new int[length + 1]; 981 } 982 System.arraycopy(colors, 0, tempColors, 0, length); 983 tempColors[length] = colors[length - 1]; 984 985 tempPositions = st.mTempPositions; 986 final float fraction = 1.0f / (length - 1); 987 if (tempPositions == null || tempPositions.length != length + 1) { 988 tempPositions = st.mTempPositions = new float[length + 1]; 989 } 990 991 final float level = getLevel() / 10000.0f; 992 for (int i = 0; i < length; i++) { 993 tempPositions[i] = i * fraction * level; 994 } 995 tempPositions[length] = 1.0f; 996 997 } 998 mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); 999 } 1000 1001 // If we don't have a solid color, the alpha channel must be 1002 // maxed out so that alpha modulation works correctly. 1003 if (st.mColorStateList == null) { 1004 mFillPaint.setColor(Color.BLACK); 1005 } 1006 } 1007 } 1008 return !mRect.isEmpty(); 1009 } 1010 1011 @Override 1012 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 1013 throws XmlPullParserException, IOException { 1014 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable); 1015 super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible); 1016 updateStateFromTypedArray(a); 1017 a.recycle(); 1018 1019 inflateChildElements(r, parser, attrs, theme); 1020 1021 mGradientState.computeOpacity(); 1022 initializeWithState(mGradientState); 1023 } 1024 1025 @Override 1026 public void applyTheme(Theme t) { 1027 super.applyTheme(t); 1028 1029 final GradientState state = mGradientState; 1030 if (state == null) { 1031 return; 1032 } 1033 1034 if (state.mThemeAttrs != null) { 1035 final TypedArray a = t.resolveAttributes( 1036 state.mThemeAttrs, R.styleable.GradientDrawable); 1037 updateStateFromTypedArray(a); 1038 a.recycle(); 1039 } 1040 1041 applyThemeChildElements(t); 1042 1043 state.computeOpacity(); 1044 } 1045 1046 /** 1047 * Updates the constant state from the values in the typed array. 1048 */ 1049 private void updateStateFromTypedArray(TypedArray a) { 1050 final GradientState state = mGradientState; 1051 1052 // Account for any configuration changes. 1053 state.mChangingConfigurations |= a.getChangingConfigurations(); 1054 1055 // Extract the theme attributes, if any. 1056 state.mThemeAttrs = a.extractThemeAttrs(); 1057 1058 state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape); 1059 state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither); 1060 1061 if (state.mShape == RING) { 1062 state.mInnerRadius = a.getDimensionPixelSize( 1063 R.styleable.GradientDrawable_innerRadius, state.mInnerRadius); 1064 1065 if (state.mInnerRadius == -1) { 1066 state.mInnerRadiusRatio = a.getFloat( 1067 R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio); 1068 } 1069 1070 state.mThickness = a.getDimensionPixelSize( 1071 R.styleable.GradientDrawable_thickness, state.mThickness); 1072 1073 if (state.mThickness == -1) { 1074 state.mThicknessRatio = a.getFloat( 1075 R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio); 1076 } 1077 1078 state.mUseLevelForShape = a.getBoolean( 1079 R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); 1080 } 1081 1082 final int tintMode = a.getInt(R.styleable.GradientDrawable_tintMode, -1); 1083 if (tintMode != -1) { 1084 state.mTintMode = Drawable.parseTintMode(tintMode, PorterDuff.Mode.SRC_IN); 1085 } 1086 1087 final ColorStateList tint = a.getColorStateList(R.styleable.GradientDrawable_tint); 1088 if (tint != null) { 1089 state.mTint = tint; 1090 } 1091 1092 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 1093 } 1094 1095 @Override 1096 public boolean canApplyTheme() { 1097 return (mGradientState != null && mGradientState.canApplyTheme()) || super.canApplyTheme(); 1098 } 1099 1100 private void applyThemeChildElements(Theme t) { 1101 final GradientState st = mGradientState; 1102 1103 if (st.mAttrSize != null) { 1104 final TypedArray a = t.resolveAttributes( 1105 st.mAttrSize, R.styleable.GradientDrawableSize); 1106 updateGradientDrawableSize(a); 1107 a.recycle(); 1108 } 1109 1110 if (st.mAttrGradient != null) { 1111 final TypedArray a = t.resolveAttributes( 1112 st.mAttrGradient, R.styleable.GradientDrawableGradient); 1113 try { 1114 updateGradientDrawableGradient(t.getResources(), a); 1115 } catch (XmlPullParserException e) { 1116 throw new RuntimeException(e); 1117 } finally { 1118 a.recycle(); 1119 } 1120 } 1121 1122 if (st.mAttrSolid != null) { 1123 final TypedArray a = t.resolveAttributes( 1124 st.mAttrSolid, R.styleable.GradientDrawableSolid); 1125 updateGradientDrawableSolid(a); 1126 a.recycle(); 1127 } 1128 1129 if (st.mAttrStroke != null) { 1130 final TypedArray a = t.resolveAttributes( 1131 st.mAttrStroke, R.styleable.GradientDrawableStroke); 1132 updateGradientDrawableStroke(a); 1133 a.recycle(); 1134 } 1135 1136 if (st.mAttrCorners != null) { 1137 final TypedArray a = t.resolveAttributes( 1138 st.mAttrCorners, R.styleable.DrawableCorners); 1139 updateDrawableCorners(a); 1140 a.recycle(); 1141 } 1142 1143 if (st.mAttrPadding != null) { 1144 final TypedArray a = t.resolveAttributes( 1145 st.mAttrPadding, R.styleable.GradientDrawablePadding); 1146 updateGradientDrawablePadding(a); 1147 a.recycle(); 1148 } 1149 } 1150 1151 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 1152 Theme theme) throws XmlPullParserException, IOException { 1153 TypedArray a; 1154 int type; 1155 1156 final int innerDepth = parser.getDepth() + 1; 1157 int depth; 1158 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1159 && ((depth=parser.getDepth()) >= innerDepth 1160 || type != XmlPullParser.END_TAG)) { 1161 if (type != XmlPullParser.START_TAG) { 1162 continue; 1163 } 1164 1165 if (depth > innerDepth) { 1166 continue; 1167 } 1168 1169 String name = parser.getName(); 1170 1171 if (name.equals("size")) { 1172 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize); 1173 updateGradientDrawableSize(a); 1174 a.recycle(); 1175 } else if (name.equals("gradient")) { 1176 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient); 1177 updateGradientDrawableGradient(r, a); 1178 a.recycle(); 1179 } else if (name.equals("solid")) { 1180 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid); 1181 updateGradientDrawableSolid(a); 1182 a.recycle(); 1183 } else if (name.equals("stroke")) { 1184 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke); 1185 updateGradientDrawableStroke(a); 1186 a.recycle(); 1187 } else if (name.equals("corners")) { 1188 a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners); 1189 updateDrawableCorners(a); 1190 a.recycle(); 1191 } else if (name.equals("padding")) { 1192 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding); 1193 updateGradientDrawablePadding(a); 1194 a.recycle(); 1195 } else { 1196 Log.w("drawable", "Bad element under <shape>: " + name); 1197 } 1198 } 1199 } 1200 1201 private void updateGradientDrawablePadding(TypedArray a) { 1202 final GradientState st = mGradientState; 1203 1204 // Account for any configuration changes. 1205 st.mChangingConfigurations |= a.getChangingConfigurations(); 1206 1207 // Extract the theme attributes, if any. 1208 st.mAttrPadding = a.extractThemeAttrs(); 1209 1210 if (st.mPadding == null) { 1211 st.mPadding = new Rect(); 1212 } 1213 1214 final Rect pad = st.mPadding; 1215 pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left), 1216 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top), 1217 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right), 1218 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom)); 1219 mPadding = pad; 1220 } 1221 1222 private void updateDrawableCorners(TypedArray a) { 1223 final GradientState st = mGradientState; 1224 1225 // Account for any configuration changes. 1226 st.mChangingConfigurations |= a.getChangingConfigurations(); 1227 1228 // Extract the theme attributes, if any. 1229 st.mAttrCorners = a.extractThemeAttrs(); 1230 1231 final int radius = a.getDimensionPixelSize( 1232 R.styleable.DrawableCorners_radius, (int) st.mRadius); 1233 setCornerRadius(radius); 1234 1235 // TODO: Update these to be themeable. 1236 final int topLeftRadius = a.getDimensionPixelSize( 1237 R.styleable.DrawableCorners_topLeftRadius, radius); 1238 final int topRightRadius = a.getDimensionPixelSize( 1239 R.styleable.DrawableCorners_topRightRadius, radius); 1240 final int bottomLeftRadius = a.getDimensionPixelSize( 1241 R.styleable.DrawableCorners_bottomLeftRadius, radius); 1242 final int bottomRightRadius = a.getDimensionPixelSize( 1243 R.styleable.DrawableCorners_bottomRightRadius, radius); 1244 if (topLeftRadius != radius || topRightRadius != radius || 1245 bottomLeftRadius != radius || bottomRightRadius != radius) { 1246 // The corner radii are specified in clockwise order (see Path.addRoundRect()) 1247 setCornerRadii(new float[] { 1248 topLeftRadius, topLeftRadius, 1249 topRightRadius, topRightRadius, 1250 bottomRightRadius, bottomRightRadius, 1251 bottomLeftRadius, bottomLeftRadius 1252 }); 1253 } 1254 } 1255 1256 private void updateGradientDrawableStroke(TypedArray a) { 1257 final GradientState st = mGradientState; 1258 1259 // Account for any configuration changes. 1260 st.mChangingConfigurations |= a.getChangingConfigurations(); 1261 1262 // Extract the theme attributes, if any. 1263 st.mAttrStroke = a.extractThemeAttrs(); 1264 1265 // We have an explicit stroke defined, so the default stroke width 1266 // must be at least 0 or the current stroke width. 1267 final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth); 1268 final int width = a.getDimensionPixelSize( 1269 R.styleable.GradientDrawableStroke_width, defaultStrokeWidth); 1270 final float dashWidth = a.getDimension( 1271 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth); 1272 1273 ColorStateList colorStateList = a.getColorStateList( 1274 R.styleable.GradientDrawableStroke_color); 1275 if (colorStateList == null) { 1276 colorStateList = st.mStrokeColorStateList; 1277 } 1278 1279 if (dashWidth != 0.0f) { 1280 final float dashGap = a.getDimension( 1281 R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap); 1282 setStroke(width, colorStateList, dashWidth, dashGap); 1283 } else { 1284 setStroke(width, colorStateList); 1285 } 1286 } 1287 1288 private void updateGradientDrawableSolid(TypedArray a) { 1289 final GradientState st = mGradientState; 1290 1291 // Account for any configuration changes. 1292 st.mChangingConfigurations |= a.getChangingConfigurations(); 1293 1294 // Extract the theme attributes, if any. 1295 st.mAttrSolid = a.extractThemeAttrs(); 1296 1297 final ColorStateList colorStateList = a.getColorStateList( 1298 R.styleable.GradientDrawableSolid_color); 1299 if (colorStateList != null) { 1300 setColor(colorStateList); 1301 } 1302 } 1303 1304 private void updateGradientDrawableGradient(Resources r, TypedArray a) 1305 throws XmlPullParserException { 1306 final GradientState st = mGradientState; 1307 1308 // Account for any configuration changes. 1309 st.mChangingConfigurations |= a.getChangingConfigurations(); 1310 1311 // Extract the theme attributes, if any. 1312 st.mAttrGradient = a.extractThemeAttrs(); 1313 1314 st.mCenterX = getFloatOrFraction( 1315 a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX); 1316 st.mCenterY = getFloatOrFraction( 1317 a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY); 1318 st.mUseLevel = a.getBoolean( 1319 R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel); 1320 st.mGradient = a.getInt( 1321 R.styleable.GradientDrawableGradient_type, st.mGradient); 1322 1323 // TODO: Update these to be themeable. 1324 final int startColor = a.getColor( 1325 R.styleable.GradientDrawableGradient_startColor, 0); 1326 final boolean hasCenterColor = a.hasValue( 1327 R.styleable.GradientDrawableGradient_centerColor); 1328 final int centerColor = a.getColor( 1329 R.styleable.GradientDrawableGradient_centerColor, 0); 1330 final int endColor = a.getColor( 1331 R.styleable.GradientDrawableGradient_endColor, 0); 1332 1333 if (hasCenterColor) { 1334 st.mColors = new int[3]; 1335 st.mColors[0] = startColor; 1336 st.mColors[1] = centerColor; 1337 st.mColors[2] = endColor; 1338 1339 st.mPositions = new float[3]; 1340 st.mPositions[0] = 0.0f; 1341 // Since 0.5f is default value, try to take the one that isn't 0.5f 1342 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; 1343 st.mPositions[2] = 1f; 1344 } else { 1345 st.mColors = new int[2]; 1346 st.mColors[0] = startColor; 1347 st.mColors[1] = endColor; 1348 } 1349 1350 if (st.mGradient == LINEAR_GRADIENT) { 1351 int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); 1352 angle %= 360; 1353 1354 if (angle % 45 != 0) { 1355 throw new XmlPullParserException(a.getPositionDescription() 1356 + "<gradient> tag requires 'angle' attribute to " 1357 + "be a multiple of 45"); 1358 } 1359 1360 st.mAngle = angle; 1361 1362 switch (angle) { 1363 case 0: 1364 st.mOrientation = Orientation.LEFT_RIGHT; 1365 break; 1366 case 45: 1367 st.mOrientation = Orientation.BL_TR; 1368 break; 1369 case 90: 1370 st.mOrientation = Orientation.BOTTOM_TOP; 1371 break; 1372 case 135: 1373 st.mOrientation = Orientation.BR_TL; 1374 break; 1375 case 180: 1376 st.mOrientation = Orientation.RIGHT_LEFT; 1377 break; 1378 case 225: 1379 st.mOrientation = Orientation.TR_BL; 1380 break; 1381 case 270: 1382 st.mOrientation = Orientation.TOP_BOTTOM; 1383 break; 1384 case 315: 1385 st.mOrientation = Orientation.TL_BR; 1386 break; 1387 } 1388 } else { 1389 final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); 1390 if (tv != null) { 1391 final float radius; 1392 final int radiusType; 1393 if (tv.type == TypedValue.TYPE_FRACTION) { 1394 radius = tv.getFraction(1.0f, 1.0f); 1395 1396 final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) 1397 & TypedValue.COMPLEX_UNIT_MASK; 1398 if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { 1399 radiusType = RADIUS_TYPE_FRACTION_PARENT; 1400 } else { 1401 radiusType = RADIUS_TYPE_FRACTION; 1402 } 1403 } else if (tv.type == TypedValue.TYPE_DIMENSION) { 1404 radius = tv.getDimension(r.getDisplayMetrics()); 1405 radiusType = RADIUS_TYPE_PIXELS; 1406 } else { 1407 radius = tv.getFloat(); 1408 radiusType = RADIUS_TYPE_PIXELS; 1409 } 1410 1411 st.mGradientRadius = radius; 1412 st.mGradientRadiusType = radiusType; 1413 } else if (st.mGradient == RADIAL_GRADIENT) { 1414 throw new XmlPullParserException( 1415 a.getPositionDescription() 1416 + "<gradient> tag requires 'gradientRadius' " 1417 + "attribute with radial type"); 1418 } 1419 } 1420 } 1421 1422 private void updateGradientDrawableSize(TypedArray a) { 1423 final GradientState st = mGradientState; 1424 1425 // Account for any configuration changes. 1426 st.mChangingConfigurations |= a.getChangingConfigurations(); 1427 1428 // Extract the theme attributes, if any. 1429 st.mAttrSize = a.extractThemeAttrs(); 1430 1431 st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth); 1432 st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight); 1433 } 1434 1435 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { 1436 TypedValue tv = a.peekValue(index); 1437 float v = defaultValue; 1438 if (tv != null) { 1439 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; 1440 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 1441 } 1442 return v; 1443 } 1444 1445 @Override 1446 public int getIntrinsicWidth() { 1447 return mGradientState.mWidth; 1448 } 1449 1450 @Override 1451 public int getIntrinsicHeight() { 1452 return mGradientState.mHeight; 1453 } 1454 1455 @Override 1456 public ConstantState getConstantState() { 1457 mGradientState.mChangingConfigurations = getChangingConfigurations(); 1458 return mGradientState; 1459 } 1460 1461 private boolean isOpaqueForState() { 1462 if (mGradientState.mStrokeWidth >= 0 && mStrokePaint != null 1463 && !isOpaque(mStrokePaint.getColor())) { 1464 return false; 1465 } 1466 1467 if (!isOpaque(mFillPaint.getColor())) { 1468 return false; 1469 } 1470 1471 return true; 1472 } 1473 1474 @Override 1475 public void getOutline(Outline outline) { 1476 final GradientState st = mGradientState; 1477 final Rect bounds = getBounds(); 1478 // only report non-zero alpha if shape being drawn is opaque 1479 outline.setAlpha(st.mOpaqueOverShape && isOpaqueForState() ? (mAlpha / 255.0f) : 0.0f); 1480 1481 switch (st.mShape) { 1482 case RECTANGLE: 1483 if (st.mRadiusArray != null) { 1484 buildPathIfDirty(); 1485 outline.setConvexPath(mPath); 1486 return; 1487 } 1488 1489 float rad = 0; 1490 if (st.mRadius > 0.0f) { 1491 // clamp the radius based on width & height, matching behavior in draw() 1492 rad = Math.min(st.mRadius, 1493 Math.min(bounds.width(), bounds.height()) * 0.5f); 1494 } 1495 outline.setRoundRect(bounds, rad); 1496 return; 1497 case OVAL: 1498 outline.setOval(bounds); 1499 return; 1500 case LINE: 1501 // Hairlines (0-width stroke) must have a non-empty outline for 1502 // shadows to draw correctly, so we'll use a very small width. 1503 final float halfStrokeWidth = mStrokePaint == null ? 1504 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f; 1505 final float centerY = bounds.centerY(); 1506 final int top = (int) Math.floor(centerY - halfStrokeWidth); 1507 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth); 1508 1509 outline.setRect(bounds.left, top, bounds.right, bottom); 1510 return; 1511 default: 1512 // TODO: support more complex shapes 1513 } 1514 } 1515 1516 @Override 1517 public Drawable mutate() { 1518 if (!mMutated && super.mutate() == this) { 1519 mGradientState = new GradientState(mGradientState); 1520 initializeWithState(mGradientState); 1521 mMutated = true; 1522 } 1523 return this; 1524 } 1525 1526 /** 1527 * @hide 1528 */ 1529 public void clearMutated() { 1530 super.clearMutated(); 1531 mMutated = false; 1532 } 1533 1534 final static class GradientState extends ConstantState { 1535 public int mChangingConfigurations; 1536 public int mShape = RECTANGLE; 1537 public int mGradient = LINEAR_GRADIENT; 1538 public int mAngle = 0; 1539 public Orientation mOrientation; 1540 public ColorStateList mColorStateList; 1541 public ColorStateList mStrokeColorStateList; 1542 public int[] mColors; 1543 public int[] mTempColors; // no need to copy 1544 public float[] mTempPositions; // no need to copy 1545 public float[] mPositions; 1546 public int mStrokeWidth = -1; // if >= 0 use stroking. 1547 public float mStrokeDashWidth = 0.0f; 1548 public float mStrokeDashGap = 0.0f; 1549 public float mRadius = 0.0f; // use this if mRadiusArray is null 1550 public float[] mRadiusArray = null; 1551 public Rect mPadding = null; 1552 public int mWidth = -1; 1553 public int mHeight = -1; 1554 public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; 1555 public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; 1556 public int mInnerRadius = -1; 1557 public int mThickness = -1; 1558 public boolean mDither = false; 1559 1560 float mCenterX = 0.5f; 1561 float mCenterY = 0.5f; 1562 float mGradientRadius = 0.5f; 1563 int mGradientRadiusType = RADIUS_TYPE_PIXELS; 1564 boolean mUseLevel = false; 1565 boolean mUseLevelForShape = true; 1566 1567 boolean mOpaqueOverBounds; 1568 boolean mOpaqueOverShape; 1569 1570 ColorStateList mTint = null; 1571 PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 1572 1573 int[] mThemeAttrs; 1574 int[] mAttrSize; 1575 int[] mAttrGradient; 1576 int[] mAttrSolid; 1577 int[] mAttrStroke; 1578 int[] mAttrCorners; 1579 int[] mAttrPadding; 1580 1581 GradientState(Orientation orientation, int[] colors) { 1582 mOrientation = orientation; 1583 setColors(colors); 1584 } 1585 1586 public GradientState(GradientState state) { 1587 mChangingConfigurations = state.mChangingConfigurations; 1588 mShape = state.mShape; 1589 mGradient = state.mGradient; 1590 mAngle = state.mAngle; 1591 mOrientation = state.mOrientation; 1592 mColorStateList = state.mColorStateList; 1593 if (state.mColors != null) { 1594 mColors = state.mColors.clone(); 1595 } 1596 if (state.mPositions != null) { 1597 mPositions = state.mPositions.clone(); 1598 } 1599 mStrokeColorStateList = state.mStrokeColorStateList; 1600 mStrokeWidth = state.mStrokeWidth; 1601 mStrokeDashWidth = state.mStrokeDashWidth; 1602 mStrokeDashGap = state.mStrokeDashGap; 1603 mRadius = state.mRadius; 1604 if (state.mRadiusArray != null) { 1605 mRadiusArray = state.mRadiusArray.clone(); 1606 } 1607 if (state.mPadding != null) { 1608 mPadding = new Rect(state.mPadding); 1609 } 1610 mWidth = state.mWidth; 1611 mHeight = state.mHeight; 1612 mInnerRadiusRatio = state.mInnerRadiusRatio; 1613 mThicknessRatio = state.mThicknessRatio; 1614 mInnerRadius = state.mInnerRadius; 1615 mThickness = state.mThickness; 1616 mDither = state.mDither; 1617 mCenterX = state.mCenterX; 1618 mCenterY = state.mCenterY; 1619 mGradientRadius = state.mGradientRadius; 1620 mGradientRadiusType = state.mGradientRadiusType; 1621 mUseLevel = state.mUseLevel; 1622 mUseLevelForShape = state.mUseLevelForShape; 1623 mOpaqueOverBounds = state.mOpaqueOverBounds; 1624 mOpaqueOverShape = state.mOpaqueOverShape; 1625 mTint = state.mTint; 1626 mTintMode = state.mTintMode; 1627 mThemeAttrs = state.mThemeAttrs; 1628 mAttrSize = state.mAttrSize; 1629 mAttrGradient = state.mAttrGradient; 1630 mAttrSolid = state.mAttrSolid; 1631 mAttrStroke = state.mAttrStroke; 1632 mAttrCorners = state.mAttrCorners; 1633 mAttrPadding = state.mAttrPadding; 1634 } 1635 1636 @Override 1637 public boolean canApplyTheme() { 1638 return mThemeAttrs != null || mAttrSize != null || mAttrGradient != null 1639 || mAttrSolid != null || mAttrStroke != null || mAttrCorners != null 1640 || mAttrPadding != null || super.canApplyTheme(); 1641 } 1642 1643 @Override 1644 public Drawable newDrawable() { 1645 return new GradientDrawable(this); 1646 } 1647 1648 @Override 1649 public Drawable newDrawable(Resources res) { 1650 return new GradientDrawable(this); 1651 } 1652 1653 @Override 1654 public int getChangingConfigurations() { 1655 return mChangingConfigurations; 1656 } 1657 1658 public void setShape(int shape) { 1659 mShape = shape; 1660 computeOpacity(); 1661 } 1662 1663 public void setGradientType(int gradient) { 1664 mGradient = gradient; 1665 } 1666 1667 public void setGradientCenter(float x, float y) { 1668 mCenterX = x; 1669 mCenterY = y; 1670 } 1671 1672 public void setColors(int[] colors) { 1673 mColors = colors; 1674 mColorStateList = null; 1675 computeOpacity(); 1676 } 1677 1678 public void setColorStateList(ColorStateList colorStateList) { 1679 mColors = null; 1680 mColorStateList = colorStateList; 1681 computeOpacity(); 1682 } 1683 1684 private void computeOpacity() { 1685 mOpaqueOverBounds = false; 1686 mOpaqueOverShape = false; 1687 1688 if (mColors != null) { 1689 for (int i = 0; i < mColors.length; i++) { 1690 if (!isOpaque(mColors[i])) { 1691 return; 1692 } 1693 } 1694 } 1695 1696 // An unfilled shape is not opaque over bounds or shape 1697 if (mColors == null && mColorStateList == null) { 1698 return; 1699 } 1700 1701 // Colors are opaque, so opaqueOverShape=true, 1702 mOpaqueOverShape = true; 1703 // and opaqueOverBounds=true if shape fills bounds 1704 mOpaqueOverBounds = mShape == RECTANGLE 1705 && mRadius <= 0 1706 && mRadiusArray == null; 1707 } 1708 1709 public void setStroke( 1710 int width, ColorStateList colorStateList, float dashWidth, float dashGap) { 1711 mStrokeWidth = width; 1712 mStrokeColorStateList = colorStateList; 1713 mStrokeDashWidth = dashWidth; 1714 mStrokeDashGap = dashGap; 1715 computeOpacity(); 1716 } 1717 1718 public void setCornerRadius(float radius) { 1719 if (radius < 0) { 1720 radius = 0; 1721 } 1722 mRadius = radius; 1723 mRadiusArray = null; 1724 } 1725 1726 public void setCornerRadii(float[] radii) { 1727 mRadiusArray = radii; 1728 if (radii == null) { 1729 mRadius = 0; 1730 } 1731 } 1732 1733 public void setSize(int width, int height) { 1734 mWidth = width; 1735 mHeight = height; 1736 } 1737 1738 public void setGradientRadius(float gradientRadius, int type) { 1739 mGradientRadius = gradientRadius; 1740 mGradientRadiusType = type; 1741 } 1742 } 1743 1744 static boolean isOpaque(int color) { 1745 return ((color >> 24) & 0xff) == 0xff; 1746 } 1747 1748 /** 1749 * Creates a new themed GradientDrawable based on the specified constant state. 1750 * <p> 1751 * The resulting drawable is guaranteed to have a new constant state. 1752 * 1753 * @param state Constant state from which the drawable inherits 1754 */ 1755 private GradientDrawable(GradientState state) { 1756 mGradientState = state; 1757 1758 initializeWithState(mGradientState); 1759 1760 mGradientIsDirty = true; 1761 mMutated = false; 1762 } 1763 1764 private void initializeWithState(GradientState state) { 1765 if (state.mColorStateList != null) { 1766 final int[] currentState = getState(); 1767 final int stateColor = state.mColorStateList.getColorForState(currentState, 0); 1768 mFillPaint.setColor(stateColor); 1769 } else if (state.mColors == null) { 1770 // If we don't have a solid color and we don't have a gradient, 1771 // the app is stroking the shape, set the color to the default 1772 // value of state.mSolidColor 1773 mFillPaint.setColor(0); 1774 } else { 1775 // Otherwise, make sure the fill alpha is maxed out. 1776 mFillPaint.setColor(Color.BLACK); 1777 } 1778 1779 mPadding = state.mPadding; 1780 1781 if (state.mStrokeWidth >= 0) { 1782 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1783 mStrokePaint.setStyle(Paint.Style.STROKE); 1784 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1785 1786 if (state.mStrokeColorStateList != null) { 1787 final int[] currentState = getState(); 1788 final int strokeStateColor = state.mStrokeColorStateList.getColorForState( 1789 currentState, 0); 1790 mStrokePaint.setColor(strokeStateColor); 1791 } 1792 1793 if (state.mStrokeDashWidth != 0.0f) { 1794 final DashPathEffect e = new DashPathEffect( 1795 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1796 mStrokePaint.setPathEffect(e); 1797 } 1798 } 1799 1800 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 1801 } 1802} 1803