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 17 package android.graphics.drawable; 18 19 import android.content.res.Resources; 20 import android.content.res.TypedArray; 21 import android.graphics.Canvas; 22 import android.graphics.ColorFilter; 23 import android.graphics.DashPathEffect; 24 import android.graphics.LinearGradient; 25 import android.graphics.Paint; 26 import android.graphics.PixelFormat; 27 import android.graphics.Rect; 28 import android.graphics.RectF; 29 import android.graphics.Shader; 30 import android.graphics.Path; 31 import android.graphics.RadialGradient; 32 import android.graphics.SweepGradient; 33 import android.util.AttributeSet; 34 import android.util.Log; 35 import android.util.TypedValue; 36 37 import org.xmlpull.v1.XmlPullParser; 38 import org.xmlpull.v1.XmlPullParserException; 39 40 import java.io.IOException; 41 42 /** 43 * A Drawable with a color gradient for buttons, backgrounds, etc. 44 * 45 * <p>It can be defined in an XML file with the <code><shape></code> element. For more 46 * information, see the guide to <a 47 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 48 * 49 * @attr ref android.R.styleable#GradientDrawable_visible 50 * @attr ref android.R.styleable#GradientDrawable_shape 51 * @attr ref android.R.styleable#GradientDrawable_innerRadiusRatio 52 * @attr ref android.R.styleable#GradientDrawable_innerRadius 53 * @attr ref android.R.styleable#GradientDrawable_thicknessRatio 54 * @attr ref android.R.styleable#GradientDrawable_thickness 55 * @attr ref android.R.styleable#GradientDrawable_useLevel 56 * @attr ref android.R.styleable#GradientDrawableSize_width 57 * @attr ref android.R.styleable#GradientDrawableSize_height 58 * @attr ref android.R.styleable#GradientDrawableGradient_startColor 59 * @attr ref android.R.styleable#GradientDrawableGradient_centerColor 60 * @attr ref android.R.styleable#GradientDrawableGradient_endColor 61 * @attr ref android.R.styleable#GradientDrawableGradient_useLevel 62 * @attr ref android.R.styleable#GradientDrawableGradient_angle 63 * @attr ref android.R.styleable#GradientDrawableGradient_type 64 * @attr ref android.R.styleable#GradientDrawableGradient_centerX 65 * @attr ref android.R.styleable#GradientDrawableGradient_centerY 66 * @attr ref android.R.styleable#GradientDrawableGradient_gradientRadius 67 * @attr ref android.R.styleable#GradientDrawableSolid_color 68 * @attr ref android.R.styleable#GradientDrawableStroke_width 69 * @attr ref android.R.styleable#GradientDrawableStroke_color 70 * @attr ref android.R.styleable#GradientDrawableStroke_dashWidth 71 * @attr ref android.R.styleable#GradientDrawableStroke_dashGap 72 * @attr ref android.R.styleable#GradientDrawablePadding_left 73 * @attr ref android.R.styleable#GradientDrawablePadding_top 74 * @attr ref android.R.styleable#GradientDrawablePadding_right 75 * @attr ref android.R.styleable#GradientDrawablePadding_bottom 76 */ 77 public class GradientDrawable extends Drawable { 78 /** 79 * Shape is a rectangle, possibly with rounded corners 80 */ 81 public static final int RECTANGLE = 0; 82 83 /** 84 * Shape is an ellipse 85 */ 86 public static final int OVAL = 1; 87 88 /** 89 * Shape is a line 90 */ 91 public static final int LINE = 2; 92 93 /** 94 * Shape is a ring. 95 */ 96 public static final int RING = 3; 97 98 /** 99 * Gradient is linear (default.) 100 */ 101 public static final int LINEAR_GRADIENT = 0; 102 103 /** 104 * Gradient is circular. 105 */ 106 public static final int RADIAL_GRADIENT = 1; 107 108 /** 109 * Gradient is a sweep. 110 */ 111 public static final int SWEEP_GRADIENT = 2; 112 113 private GradientState mGradientState; 114 115 private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 116 private Rect mPadding; 117 private Paint mStrokePaint; // optional, set by the caller 118 private ColorFilter mColorFilter; // optional, set by the caller 119 private int mAlpha = 0xFF; // modified by the caller 120 private boolean mDither; 121 122 private final Path mPath = new Path(); 123 private final RectF mRect = new RectF(); 124 125 private Paint mLayerPaint; // internal, used if we use saveLayer() 126 private boolean mRectIsDirty; // internal state 127 private boolean mMutated; 128 private Path mRingPath; 129 private boolean mPathIsDirty; 130 131 /** 132 * Controls how the gradient is oriented relative to the drawable's bounds 133 */ 134 public enum Orientation { 135 /** draw the gradient from the top to the bottom */ 136 TOP_BOTTOM, 137 /** draw the gradient from the top-right to the bottom-left */ 138 TR_BL, 139 /** draw the gradient from the right to the left */ 140 RIGHT_LEFT, 141 /** draw the gradient from the bottom-right to the top-left */ 142 BR_TL, 143 /** draw the gradient from the bottom to the top */ 144 BOTTOM_TOP, 145 /** draw the gradient from the bottom-left to the top-right */ 146 BL_TR, 147 /** draw the gradient from the left to the right */ 148 LEFT_RIGHT, 149 /** draw the gradient from the top-left to the bottom-right */ 150 TL_BR, 151 } 152 GradientDrawable()153 public GradientDrawable() { 154 this(new GradientState(Orientation.TOP_BOTTOM, null)); 155 } 156 157 /** 158 * Create a new gradient drawable given an orientation and an array 159 * of colors for the gradient. 160 */ GradientDrawable(Orientation orientation, int[] colors)161 public GradientDrawable(Orientation orientation, int[] colors) { 162 this(new GradientState(orientation, colors)); 163 } 164 165 @Override getPadding(Rect padding)166 public boolean getPadding(Rect padding) { 167 if (mPadding != null) { 168 padding.set(mPadding); 169 return true; 170 } else { 171 return super.getPadding(padding); 172 } 173 } 174 175 /** 176 * Specify radii for each of the 4 corners. For each corner, the array 177 * contains 2 values, [X_radius, Y_radius]. The corners are ordered 178 * top-left, top-right, bottom-right, bottom-left 179 */ setCornerRadii(float[] radii)180 public void setCornerRadii(float[] radii) { 181 mGradientState.setCornerRadii(radii); 182 } 183 184 /** 185 * Specify radius for the corners of the gradient. If this is > 0, then the 186 * drawable is drawn in a round-rectangle, rather than a rectangle. 187 */ setCornerRadius(float radius)188 public void setCornerRadius(float radius) { 189 mGradientState.setCornerRadius(radius); 190 } 191 192 /** 193 * Set the stroke width and color for the drawable. If width is zero, 194 * then no stroke is drawn. 195 */ setStroke(int width, int color)196 public void setStroke(int width, int color) { 197 setStroke(width, color, 0, 0); 198 } 199 setStroke(int width, int color, float dashWidth, float dashGap)200 public void setStroke(int width, int color, float dashWidth, float dashGap) { 201 mGradientState.setStroke(width, color, dashWidth, dashGap); 202 203 if (mStrokePaint == null) { 204 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 205 mStrokePaint.setStyle(Paint.Style.STROKE); 206 } 207 mStrokePaint.setStrokeWidth(width); 208 mStrokePaint.setColor(color); 209 210 DashPathEffect e = null; 211 if (dashWidth > 0) { 212 e = new DashPathEffect(new float[] { dashWidth, dashGap }, 0); 213 } 214 mStrokePaint.setPathEffect(e); 215 } 216 setSize(int width, int height)217 public void setSize(int width, int height) { 218 mGradientState.setSize(width, height); 219 } 220 setShape(int shape)221 public void setShape(int shape) { 222 mRingPath = null; 223 mGradientState.setShape(shape); 224 } 225 setGradientType(int gradient)226 public void setGradientType(int gradient) { 227 mGradientState.setGradientType(gradient); 228 mRectIsDirty = true; 229 } 230 setGradientCenter(float x, float y)231 public void setGradientCenter(float x, float y) { 232 mGradientState.setGradientCenter(x, y); 233 } 234 setGradientRadius(float gradientRadius)235 public void setGradientRadius(float gradientRadius) { 236 mGradientState.setGradientRadius(gradientRadius); 237 } 238 setUseLevel(boolean useLevel)239 public void setUseLevel(boolean useLevel) { 240 mGradientState.mUseLevel = useLevel; 241 } 242 modulateAlpha(int alpha)243 private int modulateAlpha(int alpha) { 244 int scale = mAlpha + (mAlpha >> 7); 245 return alpha * scale >> 8; 246 } 247 248 @Override draw(Canvas canvas)249 public void draw(Canvas canvas) { 250 if (!ensureValidRect()) { 251 // nothing to draw 252 return; 253 } 254 255 // remember the alpha values, in case we temporarily overwrite them 256 // when we modulate them with mAlpha 257 final int prevFillAlpha = mFillPaint.getAlpha(); 258 final int prevStrokeAlpha = mStrokePaint != null ? mStrokePaint.getAlpha() : 0; 259 // compute the modulate alpha values 260 final int currFillAlpha = modulateAlpha(prevFillAlpha); 261 final int currStrokeAlpha = modulateAlpha(prevStrokeAlpha); 262 263 final boolean haveStroke = currStrokeAlpha > 0 && mStrokePaint.getStrokeWidth() > 0; 264 final boolean haveFill = currFillAlpha > 0; 265 final GradientState st = mGradientState; 266 /* we need a layer iff we're drawing both a fill and stroke, and the 267 stroke is non-opaque, and our shapetype actually supports 268 fill+stroke. Otherwise we can just draw the stroke (if any) on top 269 of the fill (if any) without worrying about blending artifacts. 270 */ 271 final boolean useLayer = haveStroke && haveFill && st.mShape != LINE && 272 currStrokeAlpha < 255; 273 274 /* Drawing with a layer is slower than direct drawing, but it 275 allows us to apply paint effects like alpha and colorfilter to 276 the result of multiple separate draws. In our case, if the user 277 asks for a non-opaque alpha value (via setAlpha), and we're 278 stroking, then we need to apply the alpha AFTER we've drawn 279 both the fill and the stroke. 280 */ 281 if (useLayer) { 282 if (mLayerPaint == null) { 283 mLayerPaint = new Paint(); 284 } 285 mLayerPaint.setDither(mDither); 286 mLayerPaint.setAlpha(mAlpha); 287 mLayerPaint.setColorFilter(mColorFilter); 288 289 float rad = mStrokePaint.getStrokeWidth(); 290 canvas.saveLayer(mRect.left - rad, mRect.top - rad, 291 mRect.right + rad, mRect.bottom + rad, 292 mLayerPaint, Canvas.HAS_ALPHA_LAYER_SAVE_FLAG); 293 294 // don't perform the filter in our individual paints 295 // since the layer will do it for us 296 mFillPaint.setColorFilter(null); 297 mStrokePaint.setColorFilter(null); 298 } else { 299 /* if we're not using a layer, apply the dither/filter to our 300 individual paints 301 */ 302 mFillPaint.setAlpha(currFillAlpha); 303 mFillPaint.setDither(mDither); 304 mFillPaint.setColorFilter(mColorFilter); 305 if (haveStroke) { 306 mStrokePaint.setAlpha(currStrokeAlpha); 307 mStrokePaint.setDither(mDither); 308 mStrokePaint.setColorFilter(mColorFilter); 309 } 310 } 311 312 switch (st.mShape) { 313 case RECTANGLE: 314 if (st.mRadiusArray != null) { 315 mPath.reset(); 316 mPath.addRoundRect(mRect, st.mRadiusArray, 317 Path.Direction.CW); 318 canvas.drawPath(mPath, mFillPaint); 319 if (haveStroke) { 320 canvas.drawPath(mPath, mStrokePaint); 321 } 322 } 323 else { 324 // since the caller is only giving us 1 value, we will force 325 // it to be square if the rect is too small in one dimension 326 // to show it. If we did nothing, Skia would clamp the rad 327 // independently along each axis, giving us a thin ellips 328 // if the rect were very wide but not very tall 329 float rad = st.mRadius; 330 float r = Math.min(mRect.width(), mRect.height()) * 0.5f; 331 if (rad > r) { 332 rad = r; 333 } 334 canvas.drawRoundRect(mRect, rad, rad, mFillPaint); 335 if (haveStroke) { 336 canvas.drawRoundRect(mRect, rad, rad, mStrokePaint); 337 } 338 } 339 break; 340 case OVAL: 341 canvas.drawOval(mRect, mFillPaint); 342 if (haveStroke) { 343 canvas.drawOval(mRect, mStrokePaint); 344 } 345 break; 346 case LINE: { 347 RectF r = mRect; 348 float y = r.centerY(); 349 canvas.drawLine(r.left, y, r.right, y, mStrokePaint); 350 break; 351 } 352 case RING: 353 Path path = buildRing(st); 354 canvas.drawPath(path, mFillPaint); 355 if (haveStroke) { 356 canvas.drawPath(path, mStrokePaint); 357 } 358 break; 359 } 360 361 if (useLayer) { 362 canvas.restore(); 363 } else { 364 mFillPaint.setAlpha(prevFillAlpha); 365 if (haveStroke) { 366 mStrokePaint.setAlpha(prevStrokeAlpha); 367 } 368 } 369 } 370 371 private Path buildRing(GradientState st) { 372 if (mRingPath != null && (!st.mUseLevelForShape || !mPathIsDirty)) return mRingPath; 373 mPathIsDirty = false; 374 375 float sweep = st.mUseLevelForShape ? (360.0f * getLevel() / 10000.0f) : 360f; 376 377 RectF bounds = new RectF(mRect); 378 379 float x = bounds.width() / 2.0f; 380 float y = bounds.height() / 2.0f; 381 382 float thickness = st.mThickness != -1 ? 383 st.mThickness : bounds.width() / st.mThicknessRatio; 384 // inner radius 385 float radius = st.mInnerRadius != -1 ? 386 st.mInnerRadius : bounds.width() / st.mInnerRadiusRatio; 387 388 RectF innerBounds = new RectF(bounds); 389 innerBounds.inset(x - radius, y - radius); 390 391 bounds = new RectF(innerBounds); 392 bounds.inset(-thickness, -thickness); 393 394 if (mRingPath == null) { 395 mRingPath = new Path(); 396 } else { 397 mRingPath.reset(); 398 } 399 400 final Path ringPath = mRingPath; 401 // arcTo treats the sweep angle mod 360, so check for that, since we 402 // think 360 means draw the entire oval 403 if (sweep < 360 && sweep > -360) { 404 ringPath.setFillType(Path.FillType.EVEN_ODD); 405 // inner top 406 ringPath.moveTo(x + radius, y); 407 // outer top 408 ringPath.lineTo(x + radius + thickness, y); 409 // outer arc 410 ringPath.arcTo(bounds, 0.0f, sweep, false); 411 // inner arc 412 ringPath.arcTo(innerBounds, sweep, -sweep, false); 413 ringPath.close(); 414 } else { 415 // add the entire ovals 416 ringPath.addOval(bounds, Path.Direction.CW); 417 ringPath.addOval(innerBounds, Path.Direction.CCW); 418 } 419 420 return ringPath; 421 } 422 423 public void setColor(int argb) { 424 mGradientState.setSolidColor(argb); 425 mFillPaint.setColor(argb); 426 } 427 428 @Override 429 public int getChangingConfigurations() { 430 return super.getChangingConfigurations() 431 | mGradientState.mChangingConfigurations; 432 } 433 434 @Override 435 public void setAlpha(int alpha) { 436 mAlpha = alpha; 437 } 438 439 @Override 440 public void setDither(boolean dither) { 441 mDither = dither; 442 } 443 444 @Override 445 public void setColorFilter(ColorFilter cf) { 446 mColorFilter = cf; 447 } 448 449 @Override 450 public int getOpacity() { 451 // XXX need to figure out the actual opacity... 452 return PixelFormat.TRANSLUCENT; 453 } 454 455 @Override 456 protected void onBoundsChange(Rect r) { 457 super.onBoundsChange(r); 458 mRingPath = null; 459 mPathIsDirty = true; 460 mRectIsDirty = true; 461 } 462 463 @Override 464 protected boolean onLevelChange(int level) { 465 super.onLevelChange(level); 466 mRectIsDirty = true; 467 mPathIsDirty = true; 468 invalidateSelf(); 469 return true; 470 } 471 472 /** 473 * This checks mRectIsDirty, and if it is true, recomputes both our drawing 474 * rectangle (mRect) and the gradient itself, since it depends on our 475 * rectangle too. 476 * @return true if the resulting rectangle is not empty, false otherwise 477 */ 478 private boolean ensureValidRect() { 479 if (mRectIsDirty) { 480 mRectIsDirty = false; 481 482 Rect bounds = getBounds(); 483 float inset = 0; 484 485 if (mStrokePaint != null) { 486 inset = mStrokePaint.getStrokeWidth() * 0.5f; 487 } 488 489 final GradientState st = mGradientState; 490 491 mRect.set(bounds.left + inset, bounds.top + inset, 492 bounds.right - inset, bounds.bottom - inset); 493 494 final int[] colors = st.mColors; 495 if (colors != null) { 496 RectF r = mRect; 497 float x0, x1, y0, y1; 498 499 if (st.mGradient == LINEAR_GRADIENT) { 500 final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f; 501 switch (st.mOrientation) { 502 case TOP_BOTTOM: 503 x0 = r.left; y0 = r.top; 504 x1 = x0; y1 = level * r.bottom; 505 break; 506 case TR_BL: 507 x0 = r.right; y0 = r.top; 508 x1 = level * r.left; y1 = level * r.bottom; 509 break; 510 case RIGHT_LEFT: 511 x0 = r.right; y0 = r.top; 512 x1 = level * r.left; y1 = y0; 513 break; 514 case BR_TL: 515 x0 = r.right; y0 = r.bottom; 516 x1 = level * r.left; y1 = level * r.top; 517 break; 518 case BOTTOM_TOP: 519 x0 = r.left; y0 = r.bottom; 520 x1 = x0; y1 = level * r.top; 521 break; 522 case BL_TR: 523 x0 = r.left; y0 = r.bottom; 524 x1 = level * r.right; y1 = level * r.top; 525 break; 526 case LEFT_RIGHT: 527 x0 = r.left; y0 = r.top; 528 x1 = level * r.right; y1 = y0; 529 break; 530 default:/* TL_BR */ 531 x0 = r.left; y0 = r.top; 532 x1 = level * r.right; y1 = level * r.bottom; 533 break; 534 } 535 536 mFillPaint.setShader(new LinearGradient(x0, y0, x1, y1, 537 colors, st.mPositions, Shader.TileMode.CLAMP)); 538 } else if (st.mGradient == RADIAL_GRADIENT) { 539 x0 = r.left + (r.right - r.left) * st.mCenterX; 540 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 541 542 final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f; 543 544 mFillPaint.setShader(new RadialGradient(x0, y0, 545 level * st.mGradientRadius, colors, null, 546 Shader.TileMode.CLAMP)); 547 } else if (st.mGradient == SWEEP_GRADIENT) { 548 x0 = r.left + (r.right - r.left) * st.mCenterX; 549 y0 = r.top + (r.bottom - r.top) * st.mCenterY; 550 551 int[] tempColors = colors; 552 float[] tempPositions = null; 553 554 if (st.mUseLevel) { 555 tempColors = st.mTempColors; 556 final int length = colors.length; 557 if (tempColors == null || tempColors.length != length + 1) { 558 tempColors = st.mTempColors = new int[length + 1]; 559 } 560 System.arraycopy(colors, 0, tempColors, 0, length); 561 tempColors[length] = colors[length - 1]; 562 563 tempPositions = st.mTempPositions; 564 final float fraction = 1.0f / (float) (length - 1); 565 if (tempPositions == null || tempPositions.length != length + 1) { 566 tempPositions = st.mTempPositions = new float[length + 1]; 567 } 568 569 final float level = (float) getLevel() / 10000.0f; 570 for (int i = 0; i < length; i++) { 571 tempPositions[i] = i * fraction * level; 572 } 573 tempPositions[length] = 1.0f; 574 575 } 576 mFillPaint.setShader(new SweepGradient(x0, y0, tempColors, tempPositions)); 577 } 578 } 579 } 580 return !mRect.isEmpty(); 581 } 582 583 @Override 584 public void inflate(Resources r, XmlPullParser parser, 585 AttributeSet attrs) 586 throws XmlPullParserException, IOException { 587 588 final GradientState st = mGradientState; 589 590 TypedArray a = r.obtainAttributes(attrs, 591 com.android.internal.R.styleable.GradientDrawable); 592 593 super.inflateWithAttributes(r, parser, a, 594 com.android.internal.R.styleable.GradientDrawable_visible); 595 596 int shapeType = a.getInt( 597 com.android.internal.R.styleable.GradientDrawable_shape, RECTANGLE); 598 599 if (shapeType == RING) { 600 st.mInnerRadius = a.getDimensionPixelSize( 601 com.android.internal.R.styleable.GradientDrawable_innerRadius, -1); 602 if (st.mInnerRadius == -1) { 603 st.mInnerRadiusRatio = a.getFloat( 604 com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f); 605 } 606 st.mThickness = a.getDimensionPixelSize( 607 com.android.internal.R.styleable.GradientDrawable_thickness, -1); 608 if (st.mThickness == -1) { 609 st.mThicknessRatio = a.getFloat( 610 com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f); 611 } 612 st.mUseLevelForShape = a.getBoolean( 613 com.android.internal.R.styleable.GradientDrawable_useLevel, true); 614 } 615 616 a.recycle(); 617 618 setShape(shapeType); 619 620 int type; 621 622 final int innerDepth = parser.getDepth()+1; 623 int depth; 624 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 625 && ((depth=parser.getDepth()) >= innerDepth 626 || type != XmlPullParser.END_TAG)) { 627 if (type != XmlPullParser.START_TAG) { 628 continue; 629 } 630 631 if (depth > innerDepth) { 632 continue; 633 } 634 635 String name = parser.getName(); 636 637 if (name.equals("size")) { 638 a = r.obtainAttributes(attrs, 639 com.android.internal.R.styleable.GradientDrawableSize); 640 int width = a.getDimensionPixelSize( 641 com.android.internal.R.styleable.GradientDrawableSize_width, -1); 642 int height = a.getDimensionPixelSize( 643 com.android.internal.R.styleable.GradientDrawableSize_height, -1); 644 a.recycle(); 645 setSize(width, height); 646 } else if (name.equals("gradient")) { 647 a = r.obtainAttributes(attrs, 648 com.android.internal.R.styleable.GradientDrawableGradient); 649 int startColor = a.getColor( 650 com.android.internal.R.styleable.GradientDrawableGradient_startColor, 0); 651 boolean hasCenterColor = a 652 .hasValue(com.android.internal.R.styleable.GradientDrawableGradient_centerColor); 653 int centerColor = a.getColor( 654 com.android.internal.R.styleable.GradientDrawableGradient_centerColor, 0); 655 int endColor = a.getColor( 656 com.android.internal.R.styleable.GradientDrawableGradient_endColor, 0); 657 int gradientType = a.getInt( 658 com.android.internal.R.styleable.GradientDrawableGradient_type, 659 LINEAR_GRADIENT); 660 661 st.mCenterX = getFloatOrFraction( 662 a, 663 com.android.internal.R.styleable.GradientDrawableGradient_centerX, 664 0.5f); 665 666 st.mCenterY = getFloatOrFraction( 667 a, 668 com.android.internal.R.styleable.GradientDrawableGradient_centerY, 669 0.5f); 670 671 st.mUseLevel = a.getBoolean( 672 com.android.internal.R.styleable.GradientDrawableGradient_useLevel, false); 673 st.mGradient = gradientType; 674 675 if (gradientType == LINEAR_GRADIENT) { 676 int angle = (int)a.getFloat( 677 com.android.internal.R.styleable.GradientDrawableGradient_angle, 0); 678 angle %= 360; 679 if (angle % 45 != 0) { 680 throw new XmlPullParserException(a.getPositionDescription() 681 + "<gradient> tag requires 'angle' attribute to " 682 + "be a multiple of 45"); 683 } 684 685 switch (angle) { 686 case 0: 687 st.mOrientation = Orientation.LEFT_RIGHT; 688 break; 689 case 45: 690 st.mOrientation = Orientation.BL_TR; 691 break; 692 case 90: 693 st.mOrientation = Orientation.BOTTOM_TOP; 694 break; 695 case 135: 696 st.mOrientation = Orientation.BR_TL; 697 break; 698 case 180: 699 st.mOrientation = Orientation.RIGHT_LEFT; 700 break; 701 case 225: 702 st.mOrientation = Orientation.TR_BL; 703 break; 704 case 270: 705 st.mOrientation = Orientation.TOP_BOTTOM; 706 break; 707 case 315: 708 st.mOrientation = Orientation.TL_BR; 709 break; 710 } 711 } else { 712 TypedValue tv = a.peekValue( 713 com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius); 714 if (tv != null) { 715 boolean radiusRel = tv.type == TypedValue.TYPE_FRACTION; 716 st.mGradientRadius = radiusRel ? 717 tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 718 } else if (gradientType == RADIAL_GRADIENT) { 719 throw new XmlPullParserException( 720 a.getPositionDescription() 721 + "<gradient> tag requires 'gradientRadius' " 722 + "attribute with radial type"); 723 } 724 } 725 726 a.recycle(); 727 728 if (hasCenterColor) { 729 st.mColors = new int[3]; 730 st.mColors[0] = startColor; 731 st.mColors[1] = centerColor; 732 st.mColors[2] = endColor; 733 734 st.mPositions = new float[3]; 735 st.mPositions[0] = 0.0f; 736 // Since 0.5f is default value, try to take the one that isn't 0.5f 737 st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; 738 st.mPositions[2] = 1f; 739 } else { 740 st.mColors = new int[2]; 741 st.mColors[0] = startColor; 742 st.mColors[1] = endColor; 743 } 744 745 } else if (name.equals("solid")) { 746 a = r.obtainAttributes(attrs, 747 com.android.internal.R.styleable.GradientDrawableSolid); 748 int argb = a.getColor( 749 com.android.internal.R.styleable.GradientDrawableSolid_color, 0); 750 a.recycle(); 751 setColor(argb); 752 } else if (name.equals("stroke")) { 753 a = r.obtainAttributes(attrs, 754 com.android.internal.R.styleable.GradientDrawableStroke); 755 int width = a.getDimensionPixelSize( 756 com.android.internal.R.styleable.GradientDrawableStroke_width, 0); 757 int color = a.getColor( 758 com.android.internal.R.styleable.GradientDrawableStroke_color, 0); 759 float dashWidth = a.getDimension( 760 com.android.internal.R.styleable.GradientDrawableStroke_dashWidth, 0); 761 if (dashWidth != 0.0f) { 762 float dashGap = a.getDimension( 763 com.android.internal.R.styleable.GradientDrawableStroke_dashGap, 0); 764 setStroke(width, color, dashWidth, dashGap); 765 } else { 766 setStroke(width, color); 767 } 768 a.recycle(); 769 } else if (name.equals("corners")) { 770 a = r.obtainAttributes(attrs, 771 com.android.internal.R.styleable.DrawableCorners); 772 int radius = a.getDimensionPixelSize( 773 com.android.internal.R.styleable.DrawableCorners_radius, 0); 774 setCornerRadius(radius); 775 int topLeftRadius = a.getDimensionPixelSize( 776 com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius); 777 int topRightRadius = a.getDimensionPixelSize( 778 com.android.internal.R.styleable.DrawableCorners_topRightRadius, radius); 779 int bottomLeftRadius = a.getDimensionPixelSize( 780 com.android.internal.R.styleable.DrawableCorners_bottomLeftRadius, radius); 781 int bottomRightRadius = a.getDimensionPixelSize( 782 com.android.internal.R.styleable.DrawableCorners_bottomRightRadius, radius); 783 if (topLeftRadius != radius || topRightRadius != radius || 784 bottomLeftRadius != radius || bottomRightRadius != radius) { 785 setCornerRadii(new float[] { 786 topLeftRadius, topLeftRadius, 787 topRightRadius, topRightRadius, 788 bottomLeftRadius, bottomLeftRadius, 789 bottomRightRadius, bottomRightRadius 790 }); 791 } 792 a.recycle(); 793 } else if (name.equals("padding")) { 794 a = r.obtainAttributes(attrs, 795 com.android.internal.R.styleable.GradientDrawablePadding); 796 mPadding = new Rect( 797 a.getDimensionPixelOffset( 798 com.android.internal.R.styleable.GradientDrawablePadding_left, 0), 799 a.getDimensionPixelOffset( 800 com.android.internal.R.styleable.GradientDrawablePadding_top, 0), 801 a.getDimensionPixelOffset( 802 com.android.internal.R.styleable.GradientDrawablePadding_right, 0), 803 a.getDimensionPixelOffset( 804 com.android.internal.R.styleable.GradientDrawablePadding_bottom, 0)); 805 a.recycle(); 806 mGradientState.mPadding = mPadding; 807 } else { 808 Log.w("drawable", "Bad element under <shape>: " + name); 809 } 810 } 811 } 812 813 private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { 814 TypedValue tv = a.peekValue(index); 815 float v = defaultValue; 816 if (tv != null) { 817 boolean vIsFraction = tv.type == TypedValue.TYPE_FRACTION; 818 v = vIsFraction ? tv.getFraction(1.0f, 1.0f) : tv.getFloat(); 819 } 820 return v; 821 } 822 823 @Override 824 public int getIntrinsicWidth() { 825 return mGradientState.mWidth; 826 } 827 828 @Override 829 public int getIntrinsicHeight() { 830 return mGradientState.mHeight; 831 } 832 833 @Override 834 public ConstantState getConstantState() { 835 mGradientState.mChangingConfigurations = super.getChangingConfigurations(); 836 return mGradientState; 837 } 838 839 @Override 840 public Drawable mutate() { 841 if (!mMutated && super.mutate() == this) { 842 mGradientState = new GradientState(mGradientState); 843 initializeWithState(mGradientState); 844 mMutated = true; 845 } 846 return this; 847 } 848 849 final static class GradientState extends ConstantState { 850 public int mChangingConfigurations; 851 public int mShape = RECTANGLE; 852 public int mGradient = LINEAR_GRADIENT; 853 public Orientation mOrientation; 854 public int[] mColors; 855 public int[] mTempColors; // no need to copy 856 public float[] mTempPositions; // no need to copy 857 public float[] mPositions; 858 public boolean mHasSolidColor; 859 public int mSolidColor; 860 public int mStrokeWidth = -1; // if >= 0 use stroking. 861 public int mStrokeColor; 862 public float mStrokeDashWidth; 863 public float mStrokeDashGap; 864 public float mRadius; // use this if mRadiusArray is null 865 public float[] mRadiusArray; 866 public Rect mPadding; 867 public int mWidth = -1; 868 public int mHeight = -1; 869 public float mInnerRadiusRatio; 870 public float mThicknessRatio; 871 public int mInnerRadius; 872 public int mThickness; 873 private float mCenterX = 0.5f; 874 private float mCenterY = 0.5f; 875 private float mGradientRadius = 0.5f; 876 private boolean mUseLevel; 877 private boolean mUseLevelForShape; 878 879 880 GradientState() { 881 mOrientation = Orientation.TOP_BOTTOM; 882 } 883 884 GradientState(Orientation orientation, int[] colors) { 885 mOrientation = orientation; 886 mColors = colors; 887 } 888 889 public GradientState(GradientState state) { 890 mChangingConfigurations = state.mChangingConfigurations; 891 mShape = state.mShape; 892 mGradient = state.mGradient; 893 mOrientation = state.mOrientation; 894 if (state.mColors != null) { 895 mColors = state.mColors.clone(); 896 } 897 if (state.mPositions != null) { 898 mPositions = state.mPositions.clone(); 899 } 900 mHasSolidColor = state.mHasSolidColor; 901 mStrokeWidth = state.mStrokeWidth; 902 mStrokeColor = state.mStrokeColor; 903 mStrokeDashWidth = state.mStrokeDashWidth; 904 mStrokeDashGap = state.mStrokeDashGap; 905 mRadius = state.mRadius; 906 if (state.mRadiusArray != null) { 907 mRadiusArray = state.mRadiusArray.clone(); 908 } 909 if (state.mPadding != null) { 910 mPadding = new Rect(state.mPadding); 911 } 912 mWidth = state.mWidth; 913 mHeight = state.mHeight; 914 mInnerRadiusRatio = state.mInnerRadiusRatio; 915 mThicknessRatio = state.mThicknessRatio; 916 mInnerRadius = state.mInnerRadius; 917 mThickness = state.mThickness; 918 mCenterX = state.mCenterX; 919 mCenterY = state.mCenterY; 920 mGradientRadius = state.mGradientRadius; 921 mUseLevel = state.mUseLevel; 922 mUseLevelForShape = state.mUseLevelForShape; 923 } 924 925 @Override 926 public Drawable newDrawable() { 927 return new GradientDrawable(this); 928 } 929 930 @Override 931 public Drawable newDrawable(Resources res) { 932 return new GradientDrawable(this); 933 } 934 935 @Override 936 public int getChangingConfigurations() { 937 return mChangingConfigurations; 938 } 939 940 public void setShape(int shape) { 941 mShape = shape; 942 } 943 944 public void setGradientType(int gradient) { 945 mGradient = gradient; 946 } 947 948 public void setGradientCenter(float x, float y) { 949 mCenterX = x; 950 mCenterY = y; 951 } 952 953 public void setSolidColor(int argb) { 954 mHasSolidColor = true; 955 mSolidColor = argb; 956 mColors = null; 957 } 958 959 public void setStroke(int width, int color) { 960 mStrokeWidth = width; 961 mStrokeColor = color; 962 } 963 964 public void setStroke(int width, int color, float dashWidth, float dashGap) { 965 mStrokeWidth = width; 966 mStrokeColor = color; 967 mStrokeDashWidth = dashWidth; 968 mStrokeDashGap = dashGap; 969 } 970 971 public void setCornerRadius(float radius) { 972 if (radius < 0) { 973 radius = 0; 974 } 975 mRadius = radius; 976 mRadiusArray = null; 977 } 978 979 public void setCornerRadii(float[] radii) { 980 mRadiusArray = radii; 981 if (radii == null) { 982 mRadius = 0; 983 } 984 } 985 986 public void setSize(int width, int height) { 987 mWidth = width; 988 mHeight = height; 989 } 990 991 public void setGradientRadius(float gradientRadius) { 992 mGradientRadius = gradientRadius; 993 } 994 } 995 996 private GradientDrawable(GradientState state) { 997 mGradientState = state; 998 initializeWithState(state); 999 mRectIsDirty = true; 1000 } 1001 1002 private void initializeWithState(GradientState state) { 1003 if (state.mHasSolidColor) { 1004 mFillPaint.setColor(state.mSolidColor); 1005 } 1006 mPadding = state.mPadding; 1007 if (state.mStrokeWidth >= 0) { 1008 mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 1009 mStrokePaint.setStyle(Paint.Style.STROKE); 1010 mStrokePaint.setStrokeWidth(state.mStrokeWidth); 1011 mStrokePaint.setColor(state.mStrokeColor); 1012 1013 if (state.mStrokeDashWidth != 0.0f) { 1014 DashPathEffect e = new DashPathEffect( 1015 new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0); 1016 mStrokePaint.setPathEffect(e); 1017 } 1018 } 1019 } 1020 } 1021 1022