GradientDrawable.java revision 77b5cad3efedd20f2b7cc14d87ccce1b0261960a
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 outline.setAlpha(mAlpha / 255.0f); 1417 1418 switch (st.mShape) { 1419 case RECTANGLE: 1420 if (st.mRadiusArray != null) { 1421 buildPathIfDirty(); 1422 outline.setConvexPath(mPath); 1423 return; 1424 } 1425 1426 float rad = 0; 1427 if (st.mRadius > 0.0f) { 1428 // clamp the radius based on width & height, matching behavior in draw() 1429 rad = Math.min(st.mRadius, 1430 Math.min(bounds.width(), bounds.height()) * 0.5f); 1431 } 1432 outline.setRoundRect(bounds, rad); 1433 return; 1434 case OVAL: 1435 outline.setOval(bounds); 1436 return; 1437 case LINE: 1438 // Hairlines (0-width stroke) must have a non-empty outline for 1439 // shadows to draw correctly, so we'll use a very small width. 1440 final float halfStrokeWidth = mStrokePaint == null ? 1441 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f; 1442 final float centerY = bounds.centerY(); 1443 final int top = (int) Math.floor(centerY - halfStrokeWidth); 1444 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth); 1445 1446 outline.setRect(bounds.left, top, bounds.right, bottom); 1447 return; 1448 default: 1449 // TODO: support more complex shapes 1450 } 1451 } 1452 1453 @Override 1454 public Drawable mutate() { 1455 if (!mMutated && super.mutate() == this) { 1456 mGradientState = new GradientState(mGradientState); 1457 initializeWithState(mGradientState); 1458 mMutated = true; 1459 } 1460 return this; 1461 } 1462 1463 final static class GradientState extends ConstantState { 1464 public int mChangingConfigurations; 1465 public int mShape = RECTANGLE; 1466 public int mGradient = LINEAR_GRADIENT; 1467 public int mAngle = 0; 1468 public Orientation mOrientation; 1469 public ColorStateList mColorStateList; 1470 public ColorStateList mStrokeColorStateList; 1471 public int[] mColors; 1472 public int[] mTempColors; // no need to copy 1473 public float[] mTempPositions; // no need to copy 1474 public float[] mPositions; 1475 public int mStrokeWidth = -1; // if >= 0 use stroking. 1476 public float mStrokeDashWidth = 0.0f; 1477 public float mStrokeDashGap = 0.0f; 1478 public float mRadius = 0.0f; // use this if mRadiusArray is null 1479 public float[] mRadiusArray = null; 1480 public Rect mPadding = null; 1481 public int mWidth = -1; 1482 public int mHeight = -1; 1483 public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; 1484 public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; 1485 public int mInnerRadius = -1; 1486 public int mThickness = -1; 1487 public boolean mDither = false; 1488 1489 private float mCenterX = 0.5f; 1490 private float mCenterY = 0.5f; 1491 private float mGradientRadius = 0.5f; 1492 private int mGradientRadiusType = RADIUS_TYPE_PIXELS; 1493 private boolean mUseLevel; 1494 private boolean mUseLevelForShape; 1495 private boolean mOpaque; 1496 1497 int[] mThemeAttrs; 1498 int[] mAttrSize; 1499 int[] mAttrGradient; 1500 int[] mAttrSolid; 1501 int[] mAttrStroke; 1502 int[] mAttrCorners; 1503 int[] mAttrPadding; 1504 1505 GradientState(Orientation orientation, int[] colors) { 1506 mOrientation = orientation; 1507 setColors(colors); 1508 } 1509 1510 public GradientState(GradientState state) { 1511 mChangingConfigurations = state.mChangingConfigurations; 1512 mShape = state.mShape; 1513 mGradient = state.mGradient; 1514 mAngle = state.mAngle; 1515 mOrientation = state.mOrientation; 1516 mColorStateList = state.mColorStateList; 1517 if (state.mColors != null) { 1518 mColors = state.mColors.clone(); 1519 } 1520 if (state.mPositions != null) { 1521 mPositions = state.mPositions.clone(); 1522 } 1523 mStrokeColorStateList = state.mStrokeColorStateList; 1524 mStrokeWidth = state.mStrokeWidth; 1525 mStrokeDashWidth = state.mStrokeDashWidth; 1526 mStrokeDashGap = state.mStrokeDashGap; 1527 mRadius = state.mRadius; 1528 if (state.mRadiusArray != null) { 1529 mRadiusArray = state.mRadiusArray.clone(); 1530 } 1531 if (state.mPadding != null) { 1532 mPadding = new Rect(state.mPadding); 1533 } 1534 mWidth = state.mWidth; 1535 mHeight = state.mHeight; 1536 mInnerRadiusRatio = state.mInnerRadiusRatio; 1537 mThicknessRatio = state.mThicknessRatio; 1538 mInnerRadius = state.mInnerRadius; 1539 mThickness = state.mThickness; 1540 mDither = state.mDither; 1541 mCenterX = state.mCenterX; 1542 mCenterY = state.mCenterY; 1543 mGradientRadius = state.mGradientRadius; 1544 mGradientRadiusType = state.mGradientRadiusType; 1545 mUseLevel = state.mUseLevel; 1546 mUseLevelForShape = state.mUseLevelForShape; 1547 mOpaque = state.mOpaque; 1548 mThemeAttrs = state.mThemeAttrs; 1549 mAttrSize = state.mAttrSize; 1550 mAttrGradient = state.mAttrGradient; 1551 mAttrSolid = state.mAttrSolid; 1552 mAttrStroke = state.mAttrStroke; 1553 mAttrCorners = state.mAttrCorners; 1554 mAttrPadding = state.mAttrPadding; 1555 } 1556 1557 @Override 1558 public boolean canApplyTheme() { 1559 return mThemeAttrs != null; 1560 } 1561 1562 @Override 1563 public Drawable newDrawable() { 1564 return new GradientDrawable(this, null); 1565 } 1566 1567 @Override 1568 public Drawable newDrawable(Resources res) { 1569 return new GradientDrawable(this, null); 1570 } 1571 1572 @Override 1573 public Drawable newDrawable(Resources res, Theme theme) { 1574 return new GradientDrawable(this, theme); 1575 } 1576 1577 @Override 1578 public int getChangingConfigurations() { 1579 return mChangingConfigurations; 1580 } 1581 1582 public void setShape(int shape) { 1583 mShape = shape; 1584 computeOpacity(); 1585 } 1586 1587 public void setGradientType(int gradient) { 1588 mGradient = gradient; 1589 } 1590 1591 public void setGradientCenter(float x, float y) { 1592 mCenterX = x; 1593 mCenterY = y; 1594 } 1595 1596 public void setColors(int[] colors) { 1597 mColors = colors; 1598 mColorStateList = null; 1599 computeOpacity(); 1600 } 1601 1602 public void setColorStateList(ColorStateList colorStateList) { 1603 mColors = null; 1604 mColorStateList = colorStateList; 1605 computeOpacity(); 1606 } 1607 1608 private void computeOpacity() { 1609 if (mShape != RECTANGLE) { 1610 mOpaque = false; 1611 return; 1612 } 1613 1614 if (mRadius > 0 || mRadiusArray != null) { 1615 mOpaque = false; 1616 return; 1617 } 1618 1619 if (mStrokeWidth > 0) { 1620 if (mStrokeColorStateList != null) { 1621 if (!mStrokeColorStateList.isOpaque()) { 1622 mOpaque = false; 1623 return; 1624 } 1625 } 1626 } 1627 1628 if (mColorStateList != null && !mColorStateList.isOpaque()) { 1629 mOpaque = false; 1630 return; 1631 } 1632 1633 if (mColors != null) { 1634 for (int i = 0; i < mColors.length; i++) { 1635 if (!isOpaque(mColors[i])) { 1636 mOpaque = false; 1637 return; 1638 } 1639 } 1640 } 1641 1642 mOpaque = true; 1643 } 1644 1645 private static boolean isOpaque(int color) { 1646 return ((color >> 24) & 0xff) == 0xff; 1647 } 1648 1649 public void setStroke( 1650 int width, ColorStateList colorStateList, float dashWidth, float dashGap) { 1651 mStrokeWidth = width; 1652 mStrokeColorStateList = colorStateList; 1653 mStrokeDashWidth = dashWidth; 1654 mStrokeDashGap = dashGap; 1655 computeOpacity(); 1656 } 1657 1658 public void setCornerRadius(float radius) { 1659 if (radius < 0) { 1660 radius = 0; 1661 } 1662 mRadius = radius; 1663 mRadiusArray = null; 1664 } 1665 1666 public void setCornerRadii(float[] radii) { 1667 mRadiusArray = radii; 1668 if (radii == null) { 1669 mRadius = 0; 1670 } 1671 } 1672 1673 public void setSize(int width, int height) { 1674 mWidth = width; 1675 mHeight = height; 1676 } 1677 1678 public void setGradientRadius(float gradientRadius, int type) { 1679 mGradientRadius = gradientRadius; 1680 mGradientRadiusType = type; 1681 } 1682 } 1683 1684 /** 1685 * Creates a new themed GradientDrawable based on the specified constant state. 1686 * <p> 1687 * The resulting drawable is guaranteed to have a new constant state. 1688 * 1689 * @param state Constant state from which the drawable inherits 1690 * @param theme Theme to apply to the drawable 1691 */ 1692 private GradientDrawable(GradientState state, Theme theme) { 1693 if (theme != null && state.canApplyTheme()) { 1694 // If we need to apply a theme, implicitly mutate. 1695 mGradientState = new GradientState(state); 1696 applyTheme(theme); 1697 } else { 1698 mGradientState = state; 1699 } 1700 1701 initializeWithState(state); 1702 1703 mGradientIsDirty = true; 1704 mMutated = false; 1705 } 1706 1707 private void initializeWithState(GradientState state) { 1708 if (state.mColorStateList != null) { 1709 final int[] currentState = getState(); 1710 final int stateColor = state.mColorStateList.getColorForState(currentState, 0); 1711 mFillPaint.setColor(stateColor); 1712 } else if (state.mColors == null) { 1713 // If we don't have a solid color and we don't have a gradient, 1714 // the app is stroking the shape, set the color to the default 1715 // value of state.mSolidColor 1716 mFillPaint.setColor(0); 1717 } else { 1718 // Otherwise, make sure the fill alpha is maxed out. 1719 mFillPaint.setColor(Color.BLACK); 1720 } 1721 1722 mPadding = state.mPadding; 1723 1724 if (state.mStrokeWidth >= 0) { 1725 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1726 mStrokePaint.setStyle(Paint.Style.STROKE); 1727 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1728 1729 if (state.mStrokeColorStateList != null) { 1730 final int[] currentState = getState(); 1731 final int strokeStateColor = state.mStrokeColorStateList.getColorForState( 1732 currentState, 0); 1733 mStrokePaint.setColor(strokeStateColor); 1734 } 1735 1736 if (state.mStrokeDashWidth != 0.0f) { 1737 final DashPathEffect e = new DashPathEffect( 1738 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1739 mStrokePaint.setPathEffect(e); 1740 } 1741 } 1742 } 1743} 1744