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