GradientDrawable.java revision 3aff2a0813ad77fd41b39b6a636faf2de6d78acc
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 // Extract the theme attributes, if any. 1015 state.mThemeAttrs = a.extractThemeAttrs(); 1016 1017 state.mShape = a.getInt(R.styleable.GradientDrawable_shape, state.mShape); 1018 state.mDither = a.getBoolean(R.styleable.GradientDrawable_dither, state.mDither); 1019 1020 if (state.mShape == RING) { 1021 state.mInnerRadius = a.getDimensionPixelSize( 1022 R.styleable.GradientDrawable_innerRadius, state.mInnerRadius); 1023 1024 if (state.mInnerRadius == -1) { 1025 state.mInnerRadiusRatio = a.getFloat( 1026 R.styleable.GradientDrawable_innerRadiusRatio, state.mInnerRadiusRatio); 1027 } 1028 1029 state.mThickness = a.getDimensionPixelSize( 1030 R.styleable.GradientDrawable_thickness, state.mThickness); 1031 1032 if (state.mThickness == -1) { 1033 state.mThicknessRatio = a.getFloat( 1034 R.styleable.GradientDrawable_thicknessRatio, state.mThicknessRatio); 1035 } 1036 1037 state.mUseLevelForShape = a.getBoolean( 1038 R.styleable.GradientDrawable_useLevel, state.mUseLevelForShape); 1039 } 1040 } 1041 1042 @Override 1043 public boolean canApplyTheme() { 1044 final GradientState st = mGradientState; 1045 return st != null && (st.mThemeAttrs != null || st.mAttrSize != null 1046 || st.mAttrGradient != null || st.mAttrSolid != null 1047 || st.mAttrStroke != null || st.mAttrCorners != null 1048 || st.mAttrPadding != null); 1049 } 1050 1051 private void applyThemeChildElements(Theme t) { 1052 final GradientState st = mGradientState; 1053 1054 if (st.mAttrSize != null) { 1055 final TypedArray a = t.resolveAttributes( 1056 st.mAttrSize, R.styleable.GradientDrawableSize); 1057 updateGradientDrawableSize(a); 1058 a.recycle(); 1059 } 1060 1061 if (st.mAttrGradient != null) { 1062 final TypedArray a = t.resolveAttributes( 1063 st.mAttrGradient, R.styleable.GradientDrawableGradient); 1064 try { 1065 updateGradientDrawableGradient(t.getResources(), a); 1066 } catch (XmlPullParserException e) { 1067 throw new RuntimeException(e); 1068 } finally { 1069 a.recycle(); 1070 } 1071 } 1072 1073 if (st.mAttrSolid != null) { 1074 final TypedArray a = t.resolveAttributes( 1075 st.mAttrSolid, R.styleable.GradientDrawableSolid); 1076 updateGradientDrawableSolid(a); 1077 a.recycle(); 1078 } 1079 1080 if (st.mAttrStroke != null) { 1081 final TypedArray a = t.resolveAttributes( 1082 st.mAttrStroke, R.styleable.GradientDrawableStroke); 1083 updateGradientDrawableStroke(a); 1084 a.recycle(); 1085 } 1086 1087 if (st.mAttrCorners != null) { 1088 final TypedArray a = t.resolveAttributes( 1089 st.mAttrCorners, R.styleable.DrawableCorners); 1090 updateDrawableCorners(a); 1091 a.recycle(); 1092 } 1093 1094 if (st.mAttrPadding != null) { 1095 final TypedArray a = t.resolveAttributes( 1096 st.mAttrPadding, R.styleable.GradientDrawablePadding); 1097 updateGradientDrawablePadding(a); 1098 a.recycle(); 1099 } 1100 } 1101 1102 private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, 1103 Theme theme) throws XmlPullParserException, IOException { 1104 TypedArray a; 1105 int type; 1106 1107 final int innerDepth = parser.getDepth() + 1; 1108 int depth; 1109 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 1110 && ((depth=parser.getDepth()) >= innerDepth 1111 || type != XmlPullParser.END_TAG)) { 1112 if (type != XmlPullParser.START_TAG) { 1113 continue; 1114 } 1115 1116 if (depth > innerDepth) { 1117 continue; 1118 } 1119 1120 String name = parser.getName(); 1121 1122 if (name.equals("size")) { 1123 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSize); 1124 updateGradientDrawableSize(a); 1125 a.recycle(); 1126 } else if (name.equals("gradient")) { 1127 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableGradient); 1128 updateGradientDrawableGradient(r, a); 1129 a.recycle(); 1130 } else if (name.equals("solid")) { 1131 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableSolid); 1132 updateGradientDrawableSolid(a); 1133 a.recycle(); 1134 } else if (name.equals("stroke")) { 1135 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawableStroke); 1136 updateGradientDrawableStroke(a); 1137 a.recycle(); 1138 } else if (name.equals("corners")) { 1139 a = obtainAttributes(r, theme, attrs, R.styleable.DrawableCorners); 1140 updateDrawableCorners(a); 1141 a.recycle(); 1142 } else if (name.equals("padding")) { 1143 a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawablePadding); 1144 updateGradientDrawablePadding(a); 1145 a.recycle(); 1146 } else { 1147 Log.w("drawable", "Bad element under <shape>: " + name); 1148 } 1149 } 1150 } 1151 1152 private void updateGradientDrawablePadding(TypedArray a) { 1153 final GradientState st = mGradientState; 1154 1155 // Extract the theme attributes, if any. 1156 st.mAttrPadding = a.extractThemeAttrs(); 1157 1158 if (st.mPadding == null) { 1159 st.mPadding = new Rect(); 1160 } 1161 1162 final Rect pad = st.mPadding; 1163 pad.set(a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_left, pad.left), 1164 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_top, pad.top), 1165 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_right, pad.right), 1166 a.getDimensionPixelOffset(R.styleable.GradientDrawablePadding_bottom, pad.bottom)); 1167 mPadding = pad; 1168 } 1169 1170 private void updateDrawableCorners(TypedArray a) { 1171 final GradientState st = mGradientState; 1172 1173 // Extract the theme attributes, if any. 1174 st.mAttrCorners = a.extractThemeAttrs(); 1175 1176 final int radius = a.getDimensionPixelSize( 1177 R.styleable.DrawableCorners_radius, (int) st.mRadius); 1178 setCornerRadius(radius); 1179 1180 // TODO: Update these to be themeable. 1181 final int topLeftRadius = a.getDimensionPixelSize( 1182 R.styleable.DrawableCorners_topLeftRadius, radius); 1183 final int topRightRadius = a.getDimensionPixelSize( 1184 R.styleable.DrawableCorners_topRightRadius, radius); 1185 final int bottomLeftRadius = a.getDimensionPixelSize( 1186 R.styleable.DrawableCorners_bottomLeftRadius, radius); 1187 final int bottomRightRadius = a.getDimensionPixelSize( 1188 R.styleable.DrawableCorners_bottomRightRadius, radius); 1189 if (topLeftRadius != radius || topRightRadius != radius || 1190 bottomLeftRadius != radius || bottomRightRadius != radius) { 1191 // The corner radii are specified in clockwise order (see Path.addRoundRect()) 1192 setCornerRadii(new float[] { 1193 topLeftRadius, topLeftRadius, 1194 topRightRadius, topRightRadius, 1195 bottomRightRadius, bottomRightRadius, 1196 bottomLeftRadius, bottomLeftRadius 1197 }); 1198 } 1199 } 1200 1201 private void updateGradientDrawableStroke(TypedArray a) { 1202 final GradientState st = mGradientState; 1203 1204 st.mAttrStroke = a.extractThemeAttrs(); 1205 1206 // We have an explicit stroke defined, so the default stroke width 1207 // must be at least 0 or the current stroke width. 1208 final int defaultStrokeWidth = Math.max(0, st.mStrokeWidth); 1209 final int width = a.getDimensionPixelSize( 1210 R.styleable.GradientDrawableStroke_width, defaultStrokeWidth); 1211 final float dashWidth = a.getDimension( 1212 R.styleable.GradientDrawableStroke_dashWidth, st.mStrokeDashWidth); 1213 1214 ColorStateList colorStateList = a.getColorStateList( 1215 R.styleable.GradientDrawableStroke_color); 1216 if (colorStateList == null) { 1217 colorStateList = st.mStrokeColorStateList; 1218 } 1219 1220 if (dashWidth != 0.0f) { 1221 final float dashGap = a.getDimension( 1222 R.styleable.GradientDrawableStroke_dashGap, st.mStrokeDashGap); 1223 setStroke(width, colorStateList, dashWidth, dashGap); 1224 } else { 1225 setStroke(width, colorStateList); 1226 } 1227 } 1228 1229 private void updateGradientDrawableSolid(TypedArray a) { 1230 mGradientState.mAttrSolid = a.extractThemeAttrs(); 1231 1232 final ColorStateList colorStateList = a.getColorStateList( 1233 R.styleable.GradientDrawableSolid_color); 1234 if (colorStateList != null) { 1235 setColor(colorStateList); 1236 } 1237 } 1238 1239 private void updateGradientDrawableGradient(Resources r, TypedArray a) 1240 throws XmlPullParserException { 1241 final GradientState st = mGradientState; 1242 1243 // Extract the theme attributes, if any. 1244 st.mAttrGradient = a.extractThemeAttrs(); 1245 1246 st.mCenterX = getFloatOrFraction( 1247 a, R.styleable.GradientDrawableGradient_centerX, st.mCenterX); 1248 st.mCenterY = getFloatOrFraction( 1249 a, R.styleable.GradientDrawableGradient_centerY, st.mCenterY); 1250 st.mUseLevel = a.getBoolean( 1251 R.styleable.GradientDrawableGradient_useLevel, st.mUseLevel); 1252 st.mGradient = a.getInt( 1253 R.styleable.GradientDrawableGradient_type, st.mGradient); 1254 1255 // TODO: Update these to be themeable. 1256 final int startColor = a.getColor( 1257 R.styleable.GradientDrawableGradient_startColor, 0); 1258 final boolean hasCenterColor = a.hasValue( 1259 R.styleable.GradientDrawableGradient_centerColor); 1260 final int centerColor = a.getColor( 1261 R.styleable.GradientDrawableGradient_centerColor, 0); 1262 final int endColor = a.getColor( 1263 R.styleable.GradientDrawableGradient_endColor, 0); 1264 1265 if (hasCenterColor) { 1266 st.mColors = new int[3]; 1267 st.mColors[0] = startColor; 1268 st.mColors[1] = centerColor; 1269 st.mColors[2] = endColor; 1270 1271 st.mPositions = new float[3]; 1272 st.mPositions[0] = 0.0f; 1273 // Since 0.5f is default value, try to take the one that isn't 0.5f 1274 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; 1275 st.mPositions[2] = 1f; 1276 } else { 1277 st.mColors = new int[2]; 1278 st.mColors[0] = startColor; 1279 st.mColors[1] = endColor; 1280 } 1281 1282 if (st.mGradient == LINEAR_GRADIENT) { 1283 int angle = (int) a.getFloat(R.styleable.GradientDrawableGradient_angle, st.mAngle); 1284 angle %= 360; 1285 1286 if (angle % 45 != 0) { 1287 throw new XmlPullParserException(a.getPositionDescription() 1288 + "<gradient> tag requires 'angle' attribute to " 1289 + "be a multiple of 45"); 1290 } 1291 1292 st.mAngle = angle; 1293 1294 switch (angle) { 1295 case 0: 1296 st.mOrientation = Orientation.LEFT_RIGHT; 1297 break; 1298 case 45: 1299 st.mOrientation = Orientation.BL_TR; 1300 break; 1301 case 90: 1302 st.mOrientation = Orientation.BOTTOM_TOP; 1303 break; 1304 case 135: 1305 st.mOrientation = Orientation.BR_TL; 1306 break; 1307 case 180: 1308 st.mOrientation = Orientation.RIGHT_LEFT; 1309 break; 1310 case 225: 1311 st.mOrientation = Orientation.TR_BL; 1312 break; 1313 case 270: 1314 st.mOrientation = Orientation.TOP_BOTTOM; 1315 break; 1316 case 315: 1317 st.mOrientation = Orientation.TL_BR; 1318 break; 1319 } 1320 } else { 1321 final TypedValue tv = a.peekValue(R.styleable.GradientDrawableGradient_gradientRadius); 1322 if (tv != null) { 1323 final float radius; 1324 final int radiusType; 1325 if (tv.type == TypedValue.TYPE_FRACTION) { 1326 radius = tv.getFraction(1.0f, 1.0f); 1327 1328 final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) 1329 & TypedValue.COMPLEX_UNIT_MASK; 1330 if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { 1331 radiusType = RADIUS_TYPE_FRACTION_PARENT; 1332 } else { 1333 radiusType = RADIUS_TYPE_FRACTION; 1334 } 1335 } else { 1336 radius = tv.getDimension(r.getDisplayMetrics()); 1337 radiusType = RADIUS_TYPE_PIXELS; 1338 } 1339 1340 st.mGradientRadius = radius; 1341 st.mGradientRadiusType = radiusType; 1342 } else if (st.mGradient == RADIAL_GRADIENT) { 1343 throw new XmlPullParserException( 1344 a.getPositionDescription() 1345 + "<gradient> tag requires 'gradientRadius' " 1346 + "attribute with radial type"); 1347 } 1348 } 1349 } 1350 1351 private void updateGradientDrawableSize(TypedArray a) { 1352 final GradientState st = mGradientState; 1353 1354 // Extract the theme attributes, if any. 1355 st.mAttrSize = a.extractThemeAttrs(); 1356 1357 st.mWidth = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, st.mWidth); 1358 st.mHeight = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, st.mHeight); 1359 } 1360 1361 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { 1362 TypedValue tv = a.peekValue(index); 1363 float v = defaultValue; 1364 if (tv != null) { 1365 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; 1366 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 1367 } 1368 return v; 1369 } 1370 1371 @Override 1372 public int getIntrinsicWidth() { 1373 return mGradientState.mWidth; 1374 } 1375 1376 @Override 1377 public int getIntrinsicHeight() { 1378 return mGradientState.mHeight; 1379 } 1380 1381 @Override 1382 public ConstantState getConstantState() { 1383 mGradientState.mChangingConfigurations = getChangingConfigurations(); 1384 return mGradientState; 1385 } 1386 1387 @Override 1388 public boolean getOutline(Outline outline) { 1389 final GradientState st = mGradientState; 1390 final Rect bounds = getBounds(); 1391 1392 switch (st.mShape) { 1393 case RECTANGLE: 1394 if (st.mRadiusArray != null) { 1395 buildPathIfDirty(); 1396 outline.setConvexPath(mPath); 1397 return true; 1398 } 1399 1400 float rad = 0; 1401 if (st.mRadius > 0.0f) { 1402 // clamp the radius based on width & height, matching behavior in draw() 1403 rad = Math.min(st.mRadius, 1404 Math.min(bounds.width(), bounds.height()) * 0.5f); 1405 } 1406 outline.setRoundRect(bounds, rad); 1407 return true; 1408 case OVAL: 1409 outline.setOval(bounds); 1410 return true; 1411 case LINE: 1412 // Hairlines (0-width stroke) must have a non-empty outline for 1413 // shadows to draw correctly, so we'll use a very small width. 1414 final float halfStrokeWidth = mStrokePaint == null ? 1415 0.0001f : mStrokePaint.getStrokeWidth() * 0.5f; 1416 final float centerY = bounds.centerY(); 1417 final int top = (int) Math.floor(centerY - halfStrokeWidth); 1418 final int bottom = (int) Math.ceil(centerY + halfStrokeWidth); 1419 1420 outline.setRect(bounds.left, top, bounds.right, bottom); 1421 return true; 1422 default: 1423 // TODO: investigate 1424 return false; 1425 } 1426 } 1427 1428 @Override 1429 public Drawable mutate() { 1430 if (!mMutated && super.mutate() == this) { 1431 mGradientState = new GradientState(mGradientState); 1432 initializeWithState(mGradientState); 1433 mMutated = true; 1434 } 1435 return this; 1436 } 1437 1438 final static class GradientState extends ConstantState { 1439 public int mChangingConfigurations; 1440 public int mShape = RECTANGLE; 1441 public int mGradient = LINEAR_GRADIENT; 1442 public int mAngle = 0; 1443 public Orientation mOrientation; 1444 public ColorStateList mColorStateList; 1445 public ColorStateList mStrokeColorStateList; 1446 public int[] mColors; 1447 public int[] mTempColors; // no need to copy 1448 public float[] mTempPositions; // no need to copy 1449 public float[] mPositions; 1450 public int mStrokeWidth = -1; // if >= 0 use stroking. 1451 public float mStrokeDashWidth = 0.0f; 1452 public float mStrokeDashGap = 0.0f; 1453 public float mRadius = 0.0f; // use this if mRadiusArray is null 1454 public float[] mRadiusArray = null; 1455 public Rect mPadding = null; 1456 public int mWidth = -1; 1457 public int mHeight = -1; 1458 public float mInnerRadiusRatio = DEFAULT_INNER_RADIUS_RATIO; 1459 public float mThicknessRatio = DEFAULT_THICKNESS_RATIO; 1460 public int mInnerRadius = -1; 1461 public int mThickness = -1; 1462 public boolean mDither = false; 1463 1464 private float mCenterX = 0.5f; 1465 private float mCenterY = 0.5f; 1466 private float mGradientRadius = 0.5f; 1467 private int mGradientRadiusType = RADIUS_TYPE_PIXELS; 1468 private boolean mUseLevel; 1469 private boolean mUseLevelForShape; 1470 private boolean mOpaque; 1471 1472 int[] mThemeAttrs; 1473 int[] mAttrSize; 1474 int[] mAttrGradient; 1475 int[] mAttrSolid; 1476 int[] mAttrStroke; 1477 int[] mAttrCorners; 1478 int[] mAttrPadding; 1479 1480 GradientState(Orientation orientation, int[] colors) { 1481 mOrientation = orientation; 1482 setColors(colors); 1483 } 1484 1485 public GradientState(GradientState state) { 1486 mChangingConfigurations = state.mChangingConfigurations; 1487 mShape = state.mShape; 1488 mGradient = state.mGradient; 1489 mAngle = state.mAngle; 1490 mOrientation = state.mOrientation; 1491 mColorStateList = state.mColorStateList; 1492 if (state.mColors != null) { 1493 mColors = state.mColors.clone(); 1494 } 1495 if (state.mPositions != null) { 1496 mPositions = state.mPositions.clone(); 1497 } 1498 mStrokeColorStateList = state.mStrokeColorStateList; 1499 mStrokeWidth = state.mStrokeWidth; 1500 mStrokeDashWidth = state.mStrokeDashWidth; 1501 mStrokeDashGap = state.mStrokeDashGap; 1502 mRadius = state.mRadius; 1503 if (state.mRadiusArray != null) { 1504 mRadiusArray = state.mRadiusArray.clone(); 1505 } 1506 if (state.mPadding != null) { 1507 mPadding = new Rect(state.mPadding); 1508 } 1509 mWidth = state.mWidth; 1510 mHeight = state.mHeight; 1511 mInnerRadiusRatio = state.mInnerRadiusRatio; 1512 mThicknessRatio = state.mThicknessRatio; 1513 mInnerRadius = state.mInnerRadius; 1514 mThickness = state.mThickness; 1515 mDither = state.mDither; 1516 mCenterX = state.mCenterX; 1517 mCenterY = state.mCenterY; 1518 mGradientRadius = state.mGradientRadius; 1519 mGradientRadiusType = state.mGradientRadiusType; 1520 mUseLevel = state.mUseLevel; 1521 mUseLevelForShape = state.mUseLevelForShape; 1522 mOpaque = state.mOpaque; 1523 mThemeAttrs = state.mThemeAttrs; 1524 mAttrSize = state.mAttrSize; 1525 mAttrGradient = state.mAttrGradient; 1526 mAttrSolid = state.mAttrSolid; 1527 mAttrStroke = state.mAttrStroke; 1528 mAttrCorners = state.mAttrCorners; 1529 mAttrPadding = state.mAttrPadding; 1530 } 1531 1532 @Override 1533 public boolean canApplyTheme() { 1534 return mThemeAttrs != null; 1535 } 1536 1537 @Override 1538 public Drawable newDrawable() { 1539 return new GradientDrawable(this, null); 1540 } 1541 1542 @Override 1543 public Drawable newDrawable(Resources res) { 1544 return new GradientDrawable(this, null); 1545 } 1546 1547 @Override 1548 public Drawable newDrawable(Resources res, Theme theme) { 1549 return new GradientDrawable(this, theme); 1550 } 1551 1552 @Override 1553 public int getChangingConfigurations() { 1554 return mChangingConfigurations; 1555 } 1556 1557 public void setShape(int shape) { 1558 mShape = shape; 1559 computeOpacity(); 1560 } 1561 1562 public void setGradientType(int gradient) { 1563 mGradient = gradient; 1564 } 1565 1566 public void setGradientCenter(float x, float y) { 1567 mCenterX = x; 1568 mCenterY = y; 1569 } 1570 1571 public void setColors(int[] colors) { 1572 mColors = colors; 1573 mColorStateList = null; 1574 computeOpacity(); 1575 } 1576 1577 public void setColorStateList(ColorStateList colorStateList) { 1578 mColors = null; 1579 mColorStateList = colorStateList; 1580 computeOpacity(); 1581 } 1582 1583 private void computeOpacity() { 1584 if (mShape != RECTANGLE) { 1585 mOpaque = false; 1586 return; 1587 } 1588 1589 if (mRadius > 0 || mRadiusArray != null) { 1590 mOpaque = false; 1591 return; 1592 } 1593 1594 if (mStrokeWidth > 0) { 1595 if (mStrokeColorStateList != null) { 1596 if (!mStrokeColorStateList.isOpaque()) { 1597 mOpaque = false; 1598 return; 1599 } 1600 } 1601 } 1602 1603 if (mColorStateList != null && !mColorStateList.isOpaque()) { 1604 mOpaque = false; 1605 return; 1606 } 1607 1608 if (mColors != null) { 1609 for (int i = 0; i < mColors.length; i++) { 1610 if (!isOpaque(mColors[i])) { 1611 mOpaque = false; 1612 return; 1613 } 1614 } 1615 } 1616 1617 mOpaque = true; 1618 } 1619 1620 private static boolean isOpaque(int color) { 1621 return ((color >> 24) & 0xff) == 0xff; 1622 } 1623 1624 public void setStroke( 1625 int width, ColorStateList colorStateList, float dashWidth, float dashGap) { 1626 mStrokeWidth = width; 1627 mStrokeColorStateList = colorStateList; 1628 mStrokeDashWidth = dashWidth; 1629 mStrokeDashGap = dashGap; 1630 computeOpacity(); 1631 } 1632 1633 public void setCornerRadius(float radius) { 1634 if (radius < 0) { 1635 radius = 0; 1636 } 1637 mRadius = radius; 1638 mRadiusArray = null; 1639 } 1640 1641 public void setCornerRadii(float[] radii) { 1642 mRadiusArray = radii; 1643 if (radii == null) { 1644 mRadius = 0; 1645 } 1646 } 1647 1648 public void setSize(int width, int height) { 1649 mWidth = width; 1650 mHeight = height; 1651 } 1652 1653 public void setGradientRadius(float gradientRadius, int type) { 1654 mGradientRadius = gradientRadius; 1655 mGradientRadiusType = type; 1656 } 1657 } 1658 1659 /** 1660 * Creates a new themed GradientDrawable based on the specified constant state. 1661 * <p> 1662 * The resulting drawable is guaranteed to have a new constant state. 1663 * 1664 * @param state Constant state from which the drawable inherits 1665 * @param theme Theme to apply to the drawable 1666 */ 1667 private GradientDrawable(GradientState state, Theme theme) { 1668 if (theme != null && state.canApplyTheme()) { 1669 // If we need to apply a theme, implicitly mutate. 1670 mGradientState = new GradientState(state); 1671 applyTheme(theme); 1672 } else { 1673 mGradientState = state; 1674 } 1675 1676 initializeWithState(state); 1677 1678 mGradientIsDirty = true; 1679 mMutated = false; 1680 } 1681 1682 private void initializeWithState(GradientState state) { 1683 if (state.mColorStateList != null) { 1684 final int[] currentState = getState(); 1685 final int stateColor = state.mColorStateList.getColorForState(currentState, 0); 1686 mFillPaint.setColor(stateColor); 1687 } else if (state.mColors == null) { 1688 // If we don't have a solid color and we don't have a gradient, 1689 // the app is stroking the shape, set the color to the default 1690 // value of state.mSolidColor 1691 mFillPaint.setColor(0); 1692 } else { 1693 // Otherwise, make sure the fill alpha is maxed out. 1694 mFillPaint.setColor(Color.BLACK); 1695 } 1696 1697 mPadding = state.mPadding; 1698 1699 if (state.mStrokeWidth >= 0) { 1700 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1701 mStrokePaint.setStyle(Paint.Style.STROKE); 1702 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1703 1704 if (state.mStrokeColorStateList != null) { 1705 final int[] currentState = getState(); 1706 final int strokeStateColor = state.mStrokeColorStateList.getColorForState( 1707 currentState, 0); 1708 mStrokePaint.setColor(strokeStateColor); 1709 } 1710 1711 if (state.mStrokeDashWidth != 0.0f) { 1712 final DashPathEffect e = new DashPathEffect( 1713 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1714 mStrokePaint.setPathEffect(e); 1715 } 1716 } 1717 } 1718} 1719