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