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