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