GradientDrawable.java revision 3b983a74c6bac40aad191dfcfbed930cd25a9a01
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 private boolean mDither; 139 140 private final Path mPath = new Path(); 141 private final RectF mRect = new RectF(); 142 143 private Paint mLayerPaint; // internal, used if we use saveLayer() 144 private boolean mRectIsDirty; // internal state 145 private boolean mMutated; 146 private Path mRingPath; 147 private boolean mPathIsDirty = true; 148 149 /** Current gradient radius, valid when {@link #mRectIsDirty} is false. */ 150 private float mGradientRadius; 151 152 /** 153 * Controls how the gradient is oriented relative to the drawable's bounds 154 */ 155 public enum Orientation { 156 /** draw the gradient from the top to the bottom */ 157 TOP_BOTTOM, 158 /** draw the gradient from the top-right to the bottom-left */ 159 TR_BL, 160 /** draw the gradient from the right to the left */ 161 RIGHT_LEFT, 162 /** draw the gradient from the bottom-right to the top-left */ 163 BR_TL, 164 /** draw the gradient from the bottom to the top */ 165 BOTTOM_TOP, 166 /** draw the gradient from the bottom-left to the top-right */ 167 BL_TR, 168 /** draw the gradient from the left to the right */ 169 LEFT_RIGHT, 170 /** draw the gradient from the top-left to the bottom-right */ 171 TL_BR, 172 } 173 174 public GradientDrawable() { 175 this(new GradientState(Orientation.TOP_BOTTOM, null), null); 176 } 177 178 /** 179 * Create a new gradient drawable given an orientation and an array 180 * of colors for the gradient. 181 */ 182 public GradientDrawable(Orientation orientation, int[] colors) { 183 this(new GradientState(orientation, colors), null); 184 } 185 186 @Override 187 public boolean getPadding(Rect padding) { 188 if (mPadding != null) { 189 padding.set(mPadding); 190 return true; 191 } else { 192 return super.getPadding(padding); 193 } 194 } 195 196 /** 197 * <p>Specify radii for each of the 4 corners. For each corner, the array 198 * contains 2 values, <code>[X_radius, Y_radius]</code>. The corners are ordered 199 * top-left, top-right, bottom-right, bottom-left. This property 200 * is honored only when the shape is of type {@link #RECTANGLE}.</p> 201 * <p><strong>Note</strong>: changing this property will affect all instances 202 * of a drawable loaded from a resource. It is recommended to invoke 203 * {@link #mutate()} before changing this property.</p> 204 * 205 * @param radii 4 pairs of X and Y radius for each corner, specified in pixels. 206 * The length of this array must be >= 8 207 * 208 * @see #mutate() 209 * @see #setCornerRadii(float[]) 210 * @see #setShape(int) 211 */ 212 public void setCornerRadii(float[] radii) { 213 mGradientState.setCornerRadii(radii); 214 mPathIsDirty = true; 215 invalidateSelf(); 216 } 217 218 /** 219 * <p>Specify radius for the corners of the gradient. If this is > 0, then the 220 * drawable is drawn in a round-rectangle, rather than a rectangle. This property 221 * is honored only when the shape is of type {@link #RECTANGLE}.</p> 222 * <p><strong>Note</strong>: changing this property will affect all instances 223 * of a drawable loaded from a resource. It is recommended to invoke 224 * {@link #mutate()} before changing this property.</p> 225 * 226 * @param radius The radius in pixels of the corners of the rectangle shape 227 * 228 * @see #mutate() 229 * @see #setCornerRadii(float[]) 230 * @see #setShape(int) 231 */ 232 public void setCornerRadius(float radius) { 233 mGradientState.setCornerRadius(radius); 234 mPathIsDirty = true; 235 invalidateSelf(); 236 } 237 238 /** 239 * <p>Set the stroke width and color for the drawable. If width is zero, 240 * then no stroke is drawn.</p> 241 * <p><strong>Note</strong>: changing this property will affect all instances 242 * of a drawable loaded from a resource. It is recommended to invoke 243 * {@link #mutate()} before changing this property.</p> 244 * 245 * @param width The width in pixels of the stroke 246 * @param color The color of the stroke 247 * 248 * @see #mutate() 249 * @see #setStroke(int, int, float, float) 250 */ 251 public void setStroke(int width, int color) { 252 setStroke(width, color, 0, 0); 253 } 254 255 /** 256 * <p>Set the stroke width and color state list for the drawable. If width 257 * is zero, then no stroke is drawn.</p> 258 * <p><strong>Note</strong>: changing this property will affect all instances 259 * of a drawable loaded from a resource. It is recommended to invoke 260 * {@link #mutate()} before changing this property.</p> 261 * 262 * @param width The width in pixels of the stroke 263 * @param colorStateList The color state list of the stroke 264 * 265 * @see #mutate() 266 * @see #setStroke(int, ColorStateList, float, float) 267 */ 268 public void setStroke(int width, ColorStateList colorStateList) { 269 setStroke(width, colorStateList, 0, 0); 270 } 271 272 /** 273 * <p>Set the stroke width and color for the drawable. If width is zero, 274 * then no stroke is drawn. This method can also be used to dash the stroke.</p> 275 * <p><strong>Note</strong>: changing this property will affect all instances 276 * of a drawable loaded from a resource. It is recommended to invoke 277 * {@link #mutate()} before changing this property.</p> 278 * 279 * @param width The width in pixels of the stroke 280 * @param color The color of the stroke 281 * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes 282 * @param dashGap The gap in pixels between dashes 283 * 284 * @see #mutate() 285 * @see #setStroke(int, int) 286 */ 287 public void setStroke(int width, int color, float dashWidth, float dashGap) { 288 mGradientState.setStroke(width, ColorStateList.valueOf(color), dashWidth, dashGap); 289 setStrokeInternal(width, color, dashWidth, dashGap); 290 } 291 292 /** 293 * <p>Set the stroke width and color state list for the drawable. If width 294 * is zero, then no stroke is drawn. This method can also be used to dash 295 * the stroke.</p> 296 * <p><strong>Note</strong>: changing this property will affect all instances 297 * of a drawable loaded from a resource. It is recommended to invoke 298 * {@link #mutate()} before changing this property.</p> 299 * 300 * @param width The width in pixels of the stroke 301 * @param colorStateList The color state list of the stroke 302 * @param dashWidth The length in pixels of the dashes, set to 0 to disable dashes 303 * @param dashGap The gap in pixels between dashes 304 * 305 * @see #mutate() 306 * @see #setStroke(int, ColorStateList) 307 */ 308 public void setStroke( 309 int width, ColorStateList colorStateList, float dashWidth, float dashGap) { 310 mGradientState.setStroke(width, colorStateList, dashWidth, dashGap); 311 final int color; 312 if (colorStateList == null) { 313 color = Color.TRANSPARENT; 314 } else { 315 final int[] stateSet = getState(); 316 color = colorStateList.getColorForState(stateSet, 0); 317 } 318 setStrokeInternal(width, color, dashWidth, dashGap); 319 } 320 321 private void setStrokeInternal(int width, int color, float dashWidth, float dashGap) { 322 if (mStrokePaint == null) { 323 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 324 mStrokePaint.setStyle(Paint.Style.STROKE); 325 } 326 mStrokePaint.setStrokeWidth(width); 327 mStrokePaint.setColor(color); 328 329 DashPathEffect e = null; 330 if (dashWidth > 0) { 331 e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0); 332 } 333 mStrokePaint.setPathEffect(e); 334 invalidateSelf(); 335 } 336 337 338 /** 339 * <p>Sets the size of the shape drawn by this drawable.</p> 340 * <p><strong>Note</strong>: changing this property will affect all instances 341 * of a drawable loaded from a resource. It is recommended to invoke 342 * {@link #mutate()} before changing this property.</p> 343 * 344 * @param width The width of the shape used by this drawable 345 * @param height The height of the shape used by this drawable 346 * 347 * @see #mutate() 348 * @see #setGradientType(int) 349 */ 350 public void setSize(int width, int height) { 351 mGradientState.setSize(width, height); 352 mPathIsDirty = true; 353 invalidateSelf(); 354 } 355 356 /** 357 * <p>Sets the type of shape used to draw the gradient.</p> 358 * <p><strong>Note</strong>: changing this property will affect all instances 359 * of a drawable loaded from a resource. It is recommended to invoke 360 * {@link #mutate()} before changing this property.</p> 361 * 362 * @param shape The desired shape for this drawable: {@link #LINE}, 363 * {@link #OVAL}, {@link #RECTANGLE} or {@link #RING} 364 * 365 * @see #mutate() 366 */ 367 public void setShape(int shape) { 368 mRingPath = null; 369 mPathIsDirty = true; 370 mGradientState.setShape(shape); 371 invalidateSelf(); 372 } 373 374 /** 375 * <p>Sets the type of gradient used by this drawable..</p> 376 * <p><strong>Note</strong>: changing this property will affect all instances 377 * of a drawable loaded from a resource. It is recommended to invoke 378 * {@link #mutate()} before changing this property.</p> 379 * 380 * @param gradient The type of the gradient: {@link #LINEAR_GRADIENT}, 381 * {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT} 382 * 383 * @see #mutate() 384 */ 385 public void setGradientType(int gradient) { 386 mGradientState.setGradientType(gradient); 387 mRectIsDirty = true; 388 invalidateSelf(); 389 } 390 391 /** 392 * <p>Sets the center location of the gradient. The radius is honored only when 393 * the gradient type is set to {@link #RADIAL_GRADIENT} or {@link #SWEEP_GRADIENT}.</p> 394 * <p><strong>Note</strong>: changing this property will affect all instances 395 * of a drawable loaded from a resource. It is recommended to invoke 396 * {@link #mutate()} before changing this property.</p> 397 * 398 * @param x The x coordinate of the gradient's center 399 * @param y The y coordinate of the gradient's center 400 * 401 * @see #mutate() 402 * @see #setGradientType(int) 403 */ 404 public void setGradientCenter(float x, float y) { 405 mGradientState.setGradientCenter(x, y); 406 mRectIsDirty = true; 407 invalidateSelf(); 408 } 409 410 /** 411 * <p>Sets the radius of the gradient. The radius is honored only when the 412 * gradient type is set to {@link #RADIAL_GRADIENT}.</p> 413 * <p><strong>Note</strong>: changing this property will affect all instances 414 * of a drawable loaded from a resource. It is recommended to invoke 415 * {@link #mutate()} before changing this property.</p> 416 * 417 * @param gradientRadius The radius of the gradient in pixels 418 * 419 * @see #mutate() 420 * @see #setGradientType(int) 421 */ 422 public void setGradientRadius(float gradientRadius) { 423 mGradientState.setGradientRadius(gradientRadius, TypedValue.COMPLEX_UNIT_PX); 424 mRectIsDirty = true; 425 invalidateSelf(); 426 } 427 428 /** 429 * Returns the radius of the gradient in pixels. The radius is valid only 430 * when the gradient type is set to {@link #RADIAL_GRADIENT}. 431 * 432 * @return Radius in pixels. 433 */ 434 public float getGradientRadius() { 435 if (mGradientState.mGradient != RADIAL_GRADIENT) { 436 return 0; 437 } 438 439 ensureValidRect(); 440 return mGradientRadius; 441 } 442 443 /** 444 * <p>Sets whether or not this drawable will honor its <code>level</code> 445 * property.</p> 446 * <p><strong>Note</strong>: changing this property will affect all instances 447 * of a drawable loaded from a resource. It is recommended to invoke 448 * {@link #mutate()} before changing this property.</p> 449 * 450 * @param useLevel True if this drawable should honor its level, false otherwise 451 * 452 * @see #mutate() 453 * @see #setLevel(int) 454 * @see #getLevel() 455 */ 456 public void setUseLevel(boolean useLevel) { 457 mGradientState.mUseLevel = useLevel; 458 mRectIsDirty = true; 459 invalidateSelf(); 460 } 461 462 private int modulateAlpha(int alpha) { 463 int scale = mAlpha + (mAlpha >> 7); 464 return alpha * scale >> 8; 465 } 466 467 /** 468 * Returns the orientation of the gradient defined in this drawable. 469 */ 470 public Orientation getOrientation() { 471 return mGradientState.mOrientation; 472 } 473 474 /** 475 * <p>Changes the orientation of the gradient defined in this drawable.</p> 476 * <p><strong>Note</strong>: changing orientation will affect all instances 477 * of a drawable loaded from a resource. It is recommended to invoke 478 * {@link #mutate()} before changing the orientation.</p> 479 * 480 * @param orientation The desired orientation (angle) of the gradient 481 * 482 * @see #mutate() 483 */ 484 public void setOrientation(Orientation orientation) { 485 mGradientState.mOrientation = orientation; 486 mRectIsDirty = true; 487 invalidateSelf(); 488 } 489 490 /** 491 * <p>Sets the colors used to draw the gradient. Each color is specified as an 492 * ARGB integer and the array must contain at least 2 colors.</p> 493 * <p><strong>Note</strong>: changing orientation will affect all instances 494 * of a drawable loaded from a resource. It is recommended to invoke 495 * {@link #mutate()} before changing the orientation.</p> 496 * 497 * @param colors 2 or more ARGB colors 498 * 499 * @see #mutate() 500 * @see #setColor(int) 501 */ 502 public void setColors(int[] colors) { 503 mGradientState.setColors(colors); 504 mRectIsDirty = true; 505 invalidateSelf(); 506 } 507 508 @Override 509 public void draw(Canvas canvas) { 510 if (!ensureValidRect()) { 511 // nothing to draw 512 return; 513 } 514 515 // remember the alpha values, in case we temporarily overwrite them 516 // when we modulate them with mAlpha 517 final int prevFillAlpha = mFillPaint.getAlpha(); 518 final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0; 519 // compute the modulate alpha values 520 final int currFillAlpha = modulateAlpha(prevFillAlpha); 521 final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha); 522 523 final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint != null && 524 mStrokePaint.getStrokeWidth() > 0; 525 final boolean haveFill = currFillAlpha > 0; 526 final GradientState st = mGradientState; 527 /* we need a layer iff we're drawing both a fill and stroke, and the 528 stroke is non-opaque, and our shapetype actually supports 529 fill+stroke. Otherwise we can just draw the stroke (if any) on top 530 of the fill (if any) without worrying about blending artifacts. 531 */ 532 final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && 533 currStrokeAlpha < 255 && (mAlpha < 255 || mColorFilter != null); 534 535 /* Drawing with a layer is slower than direct drawing, but it 536 allows us to apply paint effects like alpha and colorfilter to 537 the result of multiple separate draws. In our case, if the user 538 asks for a non-opaque alpha value (via setAlpha), and we're 539 stroking, then we need to apply the alpha AFTER we've drawn 540 both the fill and the stroke. 541 */ 542 if (useLayer) { 543 if (mLayerPaint == null) { 544 mLayerPaint = new Paint(); 545 } 546 mLayerPaint.setDither(mDither); 547 mLayerPaint.setAlpha(mAlpha); 548 mLayerPaint.setColorFilter(mColorFilter); 549 550 float rad = mStrokePaint.getStrokeWidth(); 551 canvas.saveLayer(mRect.left - rad, mRect.top - rad, 552 mRect.right + rad, mRect.bottom + rad, 553 mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); 554 555 // don't perform the filter in our individual paints 556 // since the layer will do it for us 557 mFillPaint.setColorFilter(null); 558 mStrokePaint.setColorFilter(null); 559 } else { 560 /* if we're not using a layer, apply the dither/filter to our 561 individual paints 562 */ 563 mFillPaint.setAlpha(currFillAlpha); 564 mFillPaint.setDither(mDither); 565 mFillPaint.setColorFilter(mColorFilter); 566 if (mColorFilter != null && mGradientState.mColorStateList == null) { 567 mFillPaint.setColor(mAlpha << 24); 568 } 569 if (haveStroke) { 570 mStrokePaint.setAlpha(currStrokeAlpha); 571 mStrokePaint.setDither(mDither); 572 mStrokePaint.setColorFilter(mColorFilter); 573 } 574 } 575 576 switch (st.mShape) { 577 case RECTANGLE: 578 if (st.mRadiusArray != null) { 579 buildPathIfDirty(); 580 canvas.drawPath(mPath, mFillPaint); 581 if (haveStroke) { 582 canvas.drawPath(mPath, mStrokePaint); 583 } 584 } else if (st.mRadius > 0.0f) { 585 // since the caller is only giving us 1 value, we will force 586 // it to be square if the rect is too small in one dimension 587 // to show it. If we did nothing, Skia would clamp the rad 588 // independently along each axis, giving us a thin ellipse 589 // if the rect were very wide but not very tall 590 float rad = Math.min(st.mRadius, 591 Math.min(mRect.width(), mRect.height()) * 0.5f); 592 canvas.drawRoundRect(mRect, rad, rad, mFillPaint); 593 if (haveStroke) { 594 canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); 595 } 596 } else { 597 if (mFillPaint.getColor() != 0 || mColorFilter != null || 598 mFillPaint.getShader() != null) { 599 canvas.drawRect(mRect, mFillPaint); 600 } 601 if (haveStroke) { 602 canvas.drawRect(mRect, mStrokePaint); 603 } 604 } 605 break; 606 case OVAL: 607 canvas.drawOval(mRect, mFillPaint); 608 if (haveStroke) { 609 canvas.drawOval(mRect, mStrokePaint); 610 } 611 break; 612 case LINE: { 613 RectF r = mRect; 614 float y = r.centerY(); 615 if (haveStroke) { 616 canvas.drawLine(r.left, y, r.right, y, mStrokePaint); 617 } 618 break; 619 } 620 case RING: 621 Path path = buildRing(st); 622 canvas.drawPath(path, mFillPaint); 623 if (haveStroke) { 624 canvas.drawPath(path, mStrokePaint); 625 } 626 break; 627 } 628 629 if (useLayer) { 630 canvas.restore(); 631 } else { 632 mFillPaint.setAlpha(prevFillAlpha); 633 if (haveStroke) { 634 mStrokePaint.setAlpha(prevStrokeAlpha); 635 } 636 } 637 } 638 639 private void buildPathIfDirty() { 640 final GradientState st = mGradientState; 641 if (mPathIsDirty || mRectIsDirty) { 642 mPath.reset(); 643 mPath.addRoundRect(mRect, st.mRadiusArray, Path.Direction.CW); 644 mPathIsDirty = mRectIsDirty = 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 != mDither) { 808 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.mOpaque) ? 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 mRectIsDirty = true; 833 } 834 835 @Override 836 protected boolean onLevelChange(int level) { 837 super.onLevelChange(level); 838 mRectIsDirty = true; 839 mPathIsDirty = true; 840 invalidateSelf(); 841 return true; 842 } 843 844 /** 845 * This checks mRectIsDirty, 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 (mRectIsDirty) { 852 mRectIsDirty = 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 // Extract the theme attributes, if any. 1015 state.mThemeAttrs = a.extractThemeAttrs(); 1016 1017 state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape); 1018 mDither = a.getBoolean(R.styleable.GradientDrawable_dither, mDither); 1019 1020 if (state.mShape == RING) { 1021 state.mInnerRadius = a.getDimensionPixelSize( 1022 R.styleable.GradientDrawable_innerRadius, state.mInnerRadius); 1023 1024 if (state.mInnerRadius == -1) { 1025 state.mInnerRadiusRatio = a.getFloat( 1026 R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio); 1027 } 1028 1029 state.mThickness = a.getDimensionPixelSize( 1030 R.styleable.GradientDrawable_thickness, state.mThickness); 1031 1032 if (state.mThickness == -1) { 1033 state.mThicknessRatio = a.getFloat( 1034 R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio); 1035 } 1036 1037 state.mUseLevelForShape = a.getBoolean( 1038 R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); 1039 } 1040 } 1041 1042 @Override 1043 public boolean canApplyTheme() { 1044 final GradientState st = mGradientState; 1045 return st != null && (st.mThemeAttrs != null || st.mAttrSize != null 1046 || st.mAttrGradient != null || st.mAttrSolid != null 1047 || st.mAttrStroke != null || st.mAttrCorners != null 1048 || st.mAttrPadding != null); 1049 } 1050 1051 private void applyThemeChildElements(Theme t) { 1052 final GradientState st = mGradientState; 1053 1054 if (st.mAttrSize != null) { 1055 final TypedArray a = t.resolveAttributes( 1056 st.mAttrSize, R.styleable.GradientDrawableSize); 1057 updateGradientDrawableSize(a); 1058 a.recycle(); 1059 } 1060 1061 if (st.mAttrGradient != null) { 1062 final TypedArray a = t.resolveAttributes( 1063 st.mAttrGradient, R.styleable.GradientDrawableGradient); 1064 try { 1065 updateGradientDrawableGradient(t.getResources(), a); 1066 } catch (XmlPullParserException e) { 1067 throw new RuntimeException(e); 1068 } finally { 1069 a.recycle(); 1070 } 1071 } 1072 1073 if (st.mAttrSolid != null) { 1074 final TypedArray a = t.resolveAttributes( 1075 st.mAttrSolid, R.styleable.GradientDrawableSolid); 1076 updateGradientDrawableSolid(a); 1077 a.recycle(); 1078 } 1079 1080 if (st.mAttrStroke != null) { 1081 final TypedArray a = t.resolveAttributes( 1082 st.mAttrStroke, R.styleable.GradientDrawableStroke); 1083 updateGradientDrawableStroke(a); 1084 a.recycle(); 1085 } 1086 1087 if (st.mAttrCorners != null) { 1088 final TypedArray a = t.resolveAttributes( 1089 st.mAttrCorners, R.styleable.DrawableCorners); 1090 updateDrawableCorners(a); 1091 a.recycle(); 1092 } 1093 1094 if (st.mAttrPadding != null) { 1095 final TypedArray a = t.resolveAttributes( 1096 st.mAttrPadding, R.styleable.GradientDrawablePadding); 1097 updateGradientDrawablePadding(a); 1098 a.recycle(); 1099 } 1100 } 1101 1102 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 1103 Theme theme) throws XmlPullParserException, IOException { 1104 TypedArray a; 1105 int type; 1106 1107 final int innerDepth = parser.getDepth() + 1; 1108 int depth; 1109 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1110 && ((depth=parser.getDepth()) >= innerDepth 1111 || type != XmlPullParser.END_TAG)) { 1112 if (type != XmlPullParser.START_TAG) { 1113 continue; 1114 } 1115 1116 if (depth > innerDepth) { 1117 continue; 1118 } 1119 1120 String name = parser.getName(); 1121 1122 if (name.equals("size")) { 1123 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize); 1124 updateGradientDrawableSize(a); 1125 a.recycle(); 1126 } else if (name.equals("gradient")) { 1127 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient); 1128 updateGradientDrawableGradient(r, a); 1129 a.recycle(); 1130 } else if (name.equals("solid")) { 1131 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid); 1132 updateGradientDrawableSolid(a); 1133 a.recycle(); 1134 } else if (name.equals("stroke")) { 1135 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke); 1136 updateGradientDrawableStroke(a); 1137 a.recycle(); 1138 } else if (name.equals("corners")) { 1139 a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners); 1140 updateDrawableCorners(a); 1141 a.recycle(); 1142 } else if (name.equals("padding")) { 1143 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding); 1144 updateGradientDrawablePadding(a); 1145 a.recycle(); 1146 } else { 1147 Log.w("drawable", "Bad element under <shape>: " + name); 1148 } 1149 } 1150 } 1151 1152 private void updateGradientDrawablePadding(TypedArray a) { 1153 final GradientState st = mGradientState; 1154 1155 // Extract the theme attributes, if any. 1156 st.mAttrPadding = a.extractThemeAttrs(); 1157 1158 if (st.mPadding == null) { 1159 st.mPadding = new Rect(); 1160 } 1161 1162 final Rect pad = st.mPadding; 1163 pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left), 1164 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top), 1165 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right), 1166 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom)); 1167 mPadding = pad; 1168 } 1169 1170 private void updateDrawableCorners(TypedArray a) { 1171 final GradientState st = mGradientState; 1172 1173 // Extract the theme attributes, if any. 1174 st.mAttrCorners = a.extractThemeAttrs(); 1175 1176 final int radius = a.getDimensionPixelSize( 1177 R.styleable.DrawableCorners_radius, (int) st.mRadius); 1178 setCornerRadius(radius); 1179 1180 // TODO: Update these to be themeable. 1181 final int topLeftRadius = a.getDimensionPixelSize( 1182 R.styleable.DrawableCorners_topLeftRadius, radius); 1183 final int topRightRadius = a.getDimensionPixelSize( 1184 R.styleable.DrawableCorners_topRightRadius, radius); 1185 final int bottomLeftRadius = a.getDimensionPixelSize( 1186 R.styleable.DrawableCorners_bottomLeftRadius, radius); 1187 final int bottomRightRadius = a.getDimensionPixelSize( 1188 R.styleable.DrawableCorners_bottomRightRadius, radius); 1189 if (topLeftRadius != radius || topRightRadius != radius || 1190 bottomLeftRadius != radius || bottomRightRadius != radius) { 1191 // The corner radii are specified in clockwise order (see Path.addRoundRect()) 1192 setCornerRadii(new float[] { 1193 topLeftRadius, topLeftRadius, 1194 topRightRadius, topRightRadius, 1195 bottomRightRadius, bottomRightRadius, 1196 bottomLeftRadius, bottomLeftRadius 1197 }); 1198 } 1199 } 1200 1201 private void updateGradientDrawableStroke(TypedArray a) { 1202 final GradientState st = mGradientState; 1203 1204 st.mAttrStroke = a.extractThemeAttrs(); 1205 1206 // We have an explicit stroke defined, so the default stroke width 1207 // must be at least 0 or the current stroke width. 1208 final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth); 1209 final int width = a.getDimensionPixelSize( 1210 R.styleable.GradientDrawableStroke_width, defaultStrokeWidth); 1211 final float dashWidth = a.getDimension( 1212 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth); 1213 1214 ColorStateList colorStateList = a.getColorStateList( 1215 R.styleable.GradientDrawableStroke_color); 1216 if (colorStateList == null) { 1217 colorStateList = st.mStrokeColorStateList; 1218 } 1219 1220 if (dashWidth != 0.0f) { 1221 final float dashGap = a.getDimension( 1222 R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap); 1223 setStroke(width, colorStateList, dashWidth, dashGap); 1224 } else { 1225 setStroke(width, colorStateList); 1226 } 1227 } 1228 1229 private void updateGradientDrawableSolid(TypedArray a) { 1230 mGradientState.mAttrSolid = a.extractThemeAttrs(); 1231 1232 final ColorStateList colorStateList = a.getColorStateList( 1233 R.styleable.GradientDrawableSolid_color); 1234 if (colorStateList != null) { 1235 setColor(colorStateList); 1236 } 1237 } 1238 1239 private void updateGradientDrawableGradient(Resources r, TypedArray a) 1240 throws XmlPullParserException { 1241 final GradientState st = mGradientState; 1242 1243 // Extract the theme attributes, if any. 1244 st.mAttrGradient = a.extractThemeAttrs(); 1245 1246 st.mCenterX = getFloatOrFraction( 1247 a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX); 1248 st.mCenterY = getFloatOrFraction( 1249 a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY); 1250 st.mUseLevel = a.getBoolean( 1251 R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel); 1252 st.mGradient = a.getInt( 1253 R.styleable.GradientDrawableGradient_type, st.mGradient); 1254 1255 // TODO: Update these to be themeable. 1256 final int startColor = a.getColor( 1257 R.styleable.GradientDrawableGradient_startColor, 0); 1258 final boolean hasCenterColor = a.hasValue( 1259 R.styleable.GradientDrawableGradient_centerColor); 1260 final int centerColor = a.getColor( 1261 R.styleable.GradientDrawableGradient_centerColor, 0); 1262 final int endColor = a.getColor( 1263 R.styleable.GradientDrawableGradient_endColor, 0); 1264 1265 if (hasCenterColor) { 1266 st.mColors = new int[3]; 1267 st.mColors[0] = startColor; 1268 st.mColors[1] = centerColor; 1269 st.mColors[2] = endColor; 1270 1271 st.mPositions = new float[3]; 1272 st.mPositions[0] = 0.0f; 1273 // Since 0.5f is default value, try to take the one that isn't 0.5f 1274 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; 1275 st.mPositions[2] = 1f; 1276 } else { 1277 st.mColors = new int[2]; 1278 st.mColors[0] = startColor; 1279 st.mColors[1] = endColor; 1280 } 1281 1282 if (st.mGradient == LINEAR_GRADIENT) { 1283 int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); 1284 angle %= 360; 1285 1286 if (angle % 45 != 0) { 1287 throw new XmlPullParserException(a.getPositionDescription() 1288 + "<gradient> tag requires 'angle' attribute to " 1289 + "be a multiple of 45"); 1290 } 1291 1292 st.mAngle = angle; 1293 1294 switch (angle) { 1295 case 0: 1296 st.mOrientation = Orientation.LEFT_RIGHT; 1297 break; 1298 case 45: 1299 st.mOrientation = Orientation.BL_TR; 1300 break; 1301 case 90: 1302 st.mOrientation = Orientation.BOTTOM_TOP; 1303 break; 1304 case 135: 1305 st.mOrientation = Orientation.BR_TL; 1306 break; 1307 case 180: 1308 st.mOrientation = Orientation.RIGHT_LEFT; 1309 break; 1310 case 225: 1311 st.mOrientation = Orientation.TR_BL; 1312 break; 1313 case 270: 1314 st.mOrientation = Orientation.TOP_BOTTOM; 1315 break; 1316 case 315: 1317 st.mOrientation = Orientation.TL_BR; 1318 break; 1319 } 1320 } else { 1321 final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); 1322 if (tv != null) { 1323 final float radius; 1324 final int radiusType; 1325 if (tv.type == TypedValue.TYPE_FRACTION) { 1326 radius = tv.getFraction(1.0f, 1.0f); 1327 1328 final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) 1329 & TypedValue.COMPLEX_UNIT_MASK; 1330 if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { 1331 radiusType = RADIUS_TYPE_FRACTION_PARENT; 1332 } else { 1333 radiusType = RADIUS_TYPE_FRACTION; 1334 } 1335 } else { 1336 radius = tv.getDimension(r.getDisplayMetrics()); 1337 radiusType = RADIUS_TYPE_PIXELS; 1338 } 1339 1340 st.mGradientRadius = radius; 1341 st.mGradientRadiusType = radiusType; 1342 } else if (st.mGradient == RADIAL_GRADIENT) { 1343 throw new XmlPullParserException( 1344 a.getPositionDescription() 1345 + "<gradient> tag requires 'gradientRadius' " 1346 + "attribute with radial type"); 1347 } 1348 } 1349 } 1350 1351 private void updateGradientDrawableSize(TypedArray a) { 1352 final GradientState st = mGradientState; 1353 1354 // Extract the theme attributes, if any. 1355 st.mAttrSize = a.extractThemeAttrs(); 1356 1357 st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth); 1358 st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight); 1359 } 1360 1361 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { 1362 TypedValue tv = a.peekValue(index); 1363 float v = defaultValue; 1364 if (tv != null) { 1365 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; 1366 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 1367 } 1368 return v; 1369 } 1370 1371 @Override 1372 public int getIntrinsicWidth() { 1373 return mGradientState.mWidth; 1374 } 1375 1376 @Override 1377 public int getIntrinsicHeight() { 1378 return mGradientState.mHeight; 1379 } 1380 1381 @Override 1382 public ConstantState getConstantState() { 1383 mGradientState.mChangingConfigurations = getChangingConfigurations(); 1384 return mGradientState; 1385 } 1386 1387 @Override 1388 public boolean getOutline(Outline outline) { 1389 final GradientState st = mGradientState; 1390 final Rect bounds = getBounds(); 1391 1392 switch (st.mShape) { 1393 case RECTANGLE: 1394 if (st.mRadiusArray != null) { 1395 buildPathIfDirty(); 1396 outline.setConvexPath(mPath); 1397 return true; 1398 } 1399 1400 float rad = 0; 1401 if (st.mRadius > 0.0f) { 1402 // clamp the radius based on width & height, matching behavior in draw() 1403 rad = Math.min(st.mRadius, 1404 Math.min(bounds.width(), bounds.height()) * 0.5f); 1405 } 1406 outline.setRoundRect(bounds, rad); 1407 return true; 1408 case OVAL: 1409 outline.setOval(bounds); 1410 return true; 1411 case LINE: 1412 // Hairlines (0-width stroke) must have a non-empty outline for 1413 // shadows to draw correctly, so we'll use a very small width. 1414 final float halfStrokeWidth = mStrokePaint == null ? 1415 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f; 1416 final float centerY = bounds.centerY(); 1417 final int top = (int) Math.floor(centerY - halfStrokeWidth); 1418 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth); 1419 1420 outline.setRect(bounds.left, top, bounds.right, bottom); 1421 return true; 1422 default: 1423 // TODO: investigate 1424 return false; 1425 } 1426 } 1427 1428 @Override 1429 public Drawable mutate() { 1430 if (!mMutated && super.mutate() == this) { 1431 mGradientState = new GradientState(mGradientState); 1432 initializeWithState(mGradientState); 1433 mMutated = true; 1434 } 1435 return this; 1436 } 1437 1438 final static class GradientState extends ConstantState { 1439 public int mChangingConfigurations; 1440 public int mShape = RECTANGLE; 1441 public int mGradient = LINEAR_GRADIENT; 1442 public int mAngle = 0; 1443 public Orientation mOrientation; 1444 public ColorStateList mColorStateList; 1445 public ColorStateList mStrokeColorStateList; 1446 public int[] mColors; 1447 public int[] mTempColors; // no need to copy 1448 public float[] mTempPositions; // no need to copy 1449 public float[] mPositions; 1450 public int mStrokeWidth = -1; // if >= 0 use stroking. 1451 public float mStrokeDashWidth = 0.0f; 1452 public float mStrokeDashGap = 0.0f; 1453 public float mRadius = 0.0f; // use this if mRadiusArray is null 1454 public float[] mRadiusArray = null; 1455 public Rect mPadding = null; 1456 public int mWidth = -1; 1457 public int mHeight = -1; 1458 public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; 1459 public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; 1460 public int mInnerRadius = -1; 1461 public int mThickness = -1; 1462 private float mCenterX = 0.5f; 1463 private float mCenterY = 0.5f; 1464 private float mGradientRadius = 0.5f; 1465 private int mGradientRadiusType = RADIUS_TYPE_PIXELS; 1466 private boolean mUseLevel; 1467 private boolean mUseLevelForShape; 1468 private boolean mOpaque; 1469 1470 int[] mThemeAttrs; 1471 int[] mAttrSize; 1472 int[] mAttrGradient; 1473 int[] mAttrSolid; 1474 int[] mAttrStroke; 1475 int[] mAttrCorners; 1476 int[] mAttrPadding; 1477 1478 GradientState(Orientation orientation, int[] colors) { 1479 mOrientation = orientation; 1480 setColors(colors); 1481 } 1482 1483 public GradientState(GradientState state) { 1484 mChangingConfigurations = state.mChangingConfigurations; 1485 mShape = state.mShape; 1486 mGradient = state.mGradient; 1487 mAngle = state.mAngle; 1488 mOrientation = state.mOrientation; 1489 mColorStateList = state.mColorStateList; 1490 if (state.mColors != null) { 1491 mColors = state.mColors.clone(); 1492 } 1493 if (state.mPositions != null) { 1494 mPositions = state.mPositions.clone(); 1495 } 1496 mStrokeColorStateList = state.mStrokeColorStateList; 1497 mStrokeWidth = state.mStrokeWidth; 1498 mStrokeDashWidth = state.mStrokeDashWidth; 1499 mStrokeDashGap = state.mStrokeDashGap; 1500 mRadius = state.mRadius; 1501 if (state.mRadiusArray != null) { 1502 mRadiusArray = state.mRadiusArray.clone(); 1503 } 1504 if (state.mPadding != null) { 1505 mPadding = new Rect(state.mPadding); 1506 } 1507 mWidth = state.mWidth; 1508 mHeight = state.mHeight; 1509 mInnerRadiusRatio = state.mInnerRadiusRatio; 1510 mThicknessRatio = state.mThicknessRatio; 1511 mInnerRadius = state.mInnerRadius; 1512 mThickness = state.mThickness; 1513 mCenterX = state.mCenterX; 1514 mCenterY = state.mCenterY; 1515 mGradientRadius = state.mGradientRadius; 1516 mGradientRadiusType = state.mGradientRadiusType; 1517 mUseLevel = state.mUseLevel; 1518 mUseLevelForShape = state.mUseLevelForShape; 1519 mOpaque = state.mOpaque; 1520 mThemeAttrs = state.mThemeAttrs; 1521 mAttrSize = state.mAttrSize; 1522 mAttrGradient = state.mAttrGradient; 1523 mAttrSolid = state.mAttrSolid; 1524 mAttrStroke = state.mAttrStroke; 1525 mAttrCorners = state.mAttrCorners; 1526 mAttrPadding = state.mAttrPadding; 1527 } 1528 1529 @Override 1530 public boolean canApplyTheme() { 1531 return mThemeAttrs != null; 1532 } 1533 1534 @Override 1535 public Drawable newDrawable() { 1536 return new GradientDrawable(this, null); 1537 } 1538 1539 @Override 1540 public Drawable newDrawable(Resources res) { 1541 return new GradientDrawable(this, null); 1542 } 1543 1544 @Override 1545 public Drawable newDrawable(Resources res, Theme theme) { 1546 return new GradientDrawable(this, theme); 1547 } 1548 1549 @Override 1550 public int getChangingConfigurations() { 1551 return mChangingConfigurations; 1552 } 1553 1554 public void setShape(int shape) { 1555 mShape = shape; 1556 computeOpacity(); 1557 } 1558 1559 public void setGradientType(int gradient) { 1560 mGradient = gradient; 1561 } 1562 1563 public void setGradientCenter(float x, float y) { 1564 mCenterX = x; 1565 mCenterY = y; 1566 } 1567 1568 public void setColors(int[] colors) { 1569 mColors = colors; 1570 mColorStateList = null; 1571 computeOpacity(); 1572 } 1573 1574 public void setColorStateList(ColorStateList colorStateList) { 1575 mColors = null; 1576 mColorStateList = colorStateList; 1577 computeOpacity(); 1578 } 1579 1580 private void computeOpacity() { 1581 if (mShape != RECTANGLE) { 1582 mOpaque = false; 1583 return; 1584 } 1585 1586 if (mRadius > 0 || mRadiusArray != null) { 1587 mOpaque = false; 1588 return; 1589 } 1590 1591 if (mStrokeWidth > 0) { 1592 if (mStrokeColorStateList != null) { 1593 if (!mStrokeColorStateList.isOpaque()) { 1594 mOpaque = false; 1595 return; 1596 } 1597 } 1598 } 1599 1600 if (mColorStateList != null && !mColorStateList.isOpaque()) { 1601 mOpaque = false; 1602 return; 1603 } 1604 1605 if (mColors != null) { 1606 for (int i = 0; i < mColors.length; i++) { 1607 if (!isOpaque(mColors[i])) { 1608 mOpaque = false; 1609 return; 1610 } 1611 } 1612 } 1613 1614 mOpaque = true; 1615 } 1616 1617 private static boolean isOpaque(int color) { 1618 return ((color >> 24) & 0xff) == 0xff; 1619 } 1620 1621 public void setStroke( 1622 int width, ColorStateList colorStateList, float dashWidth, float dashGap) { 1623 mStrokeWidth = width; 1624 mStrokeColorStateList = colorStateList; 1625 mStrokeDashWidth = dashWidth; 1626 mStrokeDashGap = dashGap; 1627 computeOpacity(); 1628 } 1629 1630 public void setCornerRadius(float radius) { 1631 if (radius < 0) { 1632 radius = 0; 1633 } 1634 mRadius = radius; 1635 mRadiusArray = null; 1636 } 1637 1638 public void setCornerRadii(float[] radii) { 1639 mRadiusArray = radii; 1640 if (radii == null) { 1641 mRadius = 0; 1642 } 1643 } 1644 1645 public void setSize(int width, int height) { 1646 mWidth = width; 1647 mHeight = height; 1648 } 1649 1650 public void setGradientRadius(float gradientRadius, int type) { 1651 mGradientRadius = gradientRadius; 1652 mGradientRadiusType = type; 1653 } 1654 } 1655 1656 /** 1657 * Creates a new themed GradientDrawable based on the specified constant state. 1658 * <p> 1659 * The resulting drawable is guaranteed to have a new constant state. 1660 * 1661 * @param state Constant state from which the drawable inherits 1662 * @param theme Theme to apply to the drawable 1663 */ 1664 private GradientDrawable(GradientState state, Theme theme) { 1665 mGradientState = new GradientState(state); 1666 if (theme != null && state.canApplyTheme()) { 1667 applyTheme(theme); 1668 } 1669 1670 initializeWithState(state); 1671 1672 mRectIsDirty = true; 1673 mMutated = false; 1674 } 1675 1676 private void initializeWithState(GradientState state) { 1677 if (state.mColorStateList != null) { 1678 final int[] currentState = getState(); 1679 final int stateColor = state.mColorStateList.getColorForState(currentState, 0); 1680 mFillPaint.setColor(stateColor); 1681 } else if (state.mColors == null) { 1682 // If we don't have a solid color and we don't have a gradient, 1683 // the app is stroking the shape, set the color to the default 1684 // value of state.mSolidColor 1685 mFillPaint.setColor(0); 1686 } else { 1687 // Otherwise, make sure the fill alpha is maxed out. 1688 mFillPaint.setColor(Color.BLACK); 1689 } 1690 1691 mPadding = state.mPadding; 1692 1693 if (state.mStrokeWidth >= 0) { 1694 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1695 mStrokePaint.setStyle(Paint.Style.STROKE); 1696 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1697 1698 if (state.mStrokeColorStateList != null) { 1699 final int[] currentState = getState(); 1700 final int strokeStateColor = state.mStrokeColorStateList.getColorForState( 1701 currentState, 0); 1702 mStrokePaint.setColor(strokeStateColor); 1703 } 1704 1705 if (state.mStrokeDashWidth != 0.0f) { 1706 final DashPathEffect e = new DashPathEffect( 1707 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1708 mStrokePaint.setPathEffect(e); 1709 } 1710 } 1711 } 1712} 1713