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