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