ShapeDrawable.java revision 4a81674b45b7250c4e2a80330371f7aa1c066d05
1/* 2 * Copyright (C) 2007 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.annotation.NonNull; 20import android.content.pm.ActivityInfo.Config; 21import android.content.res.ColorStateList; 22import android.content.res.Resources; 23import android.content.res.Resources.Theme; 24import android.content.res.TypedArray; 25import android.graphics.Canvas; 26import android.graphics.ColorFilter; 27import android.graphics.Outline; 28import android.graphics.Paint; 29import android.graphics.PixelFormat; 30import android.graphics.PorterDuff; 31import android.graphics.PorterDuff.Mode; 32import android.graphics.PorterDuffColorFilter; 33import android.graphics.Rect; 34import android.graphics.Shader; 35import android.graphics.drawable.shapes.Shape; 36import android.util.AttributeSet; 37 38import com.android.internal.R; 39 40import org.xmlpull.v1.XmlPullParser; 41import org.xmlpull.v1.XmlPullParserException; 42 43import java.io.IOException; 44 45/** 46 * A Drawable object that draws primitive shapes. A ShapeDrawable takes a 47 * {@link android.graphics.drawable.shapes.Shape} object and manages its 48 * presence on the screen. If no Shape is given, then the ShapeDrawable will 49 * default to a {@link android.graphics.drawable.shapes.RectShape}. 50 * <p> 51 * This object can be defined in an XML file with the <code><shape></code> 52 * element. 53 * </p> 54 * <div class="special reference"> <h3>Developer Guides</h3> 55 * <p> 56 * For more information about how to use ShapeDrawable, read the <a 57 * href="{@docRoot}guide/topics/graphics/2d-graphics.html#shape-drawable"> 58 * Canvas and Drawables</a> document. For more information about defining a 59 * ShapeDrawable in XML, read the 60 * <a href="{@docRoot}guide/topics/resources/drawable-resource.html#Shape"> 61 * Drawable Resources</a> document. 62 * </p> 63 * </div> 64 * 65 * @attr ref android.R.styleable#ShapeDrawablePadding_left 66 * @attr ref android.R.styleable#ShapeDrawablePadding_top 67 * @attr ref android.R.styleable#ShapeDrawablePadding_right 68 * @attr ref android.R.styleable#ShapeDrawablePadding_bottom 69 * @attr ref android.R.styleable#ShapeDrawable_color 70 * @attr ref android.R.styleable#ShapeDrawable_width 71 * @attr ref android.R.styleable#ShapeDrawable_height 72 */ 73public class ShapeDrawable extends Drawable { 74 private @NonNull ShapeState mShapeState; 75 private PorterDuffColorFilter mTintFilter; 76 private boolean mMutated; 77 78 /** 79 * ShapeDrawable constructor. 80 */ 81 public ShapeDrawable() { 82 this(new ShapeState(), null); 83 } 84 85 /** 86 * Creates a ShapeDrawable with a specified Shape. 87 * 88 * @param s the Shape that this ShapeDrawable should be 89 */ 90 public ShapeDrawable(Shape s) { 91 this(new ShapeState(), null); 92 93 mShapeState.mShape = s; 94 } 95 96 /** 97 * Returns the Shape of this ShapeDrawable. 98 */ 99 public Shape getShape() { 100 return mShapeState.mShape; 101 } 102 103 /** 104 * Sets the Shape of this ShapeDrawable. 105 */ 106 public void setShape(Shape s) { 107 mShapeState.mShape = s; 108 updateShape(); 109 } 110 111 /** 112 * Sets a ShaderFactory to which requests for a 113 * {@link android.graphics.Shader} object will be made. 114 * 115 * @param fact an instance of your ShaderFactory implementation 116 */ 117 public void setShaderFactory(ShaderFactory fact) { 118 mShapeState.mShaderFactory = fact; 119 } 120 121 /** 122 * Returns the ShaderFactory used by this ShapeDrawable for requesting a 123 * {@link android.graphics.Shader}. 124 */ 125 public ShaderFactory getShaderFactory() { 126 return mShapeState.mShaderFactory; 127 } 128 129 /** 130 * Returns the Paint used to draw the shape. 131 */ 132 public Paint getPaint() { 133 return mShapeState.mPaint; 134 } 135 136 /** 137 * Sets padding for the shape. 138 * 139 * @param left padding for the left side (in pixels) 140 * @param top padding for the top (in pixels) 141 * @param right padding for the right side (in pixels) 142 * @param bottom padding for the bottom (in pixels) 143 */ 144 public void setPadding(int left, int top, int right, int bottom) { 145 if ((left | top | right | bottom) == 0) { 146 mShapeState.mPadding = null; 147 } else { 148 if (mShapeState.mPadding == null) { 149 mShapeState.mPadding = new Rect(); 150 } 151 mShapeState.mPadding.set(left, top, right, bottom); 152 } 153 invalidateSelf(); 154 } 155 156 /** 157 * Sets padding for this shape, defined by a Rect object. Define the padding 158 * in the Rect object as: left, top, right, bottom. 159 */ 160 public void setPadding(Rect padding) { 161 if (padding == null) { 162 mShapeState.mPadding = null; 163 } else { 164 if (mShapeState.mPadding == null) { 165 mShapeState.mPadding = new Rect(); 166 } 167 mShapeState.mPadding.set(padding); 168 } 169 invalidateSelf(); 170 } 171 172 /** 173 * Sets the intrinsic (default) width for this shape. 174 * 175 * @param width the intrinsic width (in pixels) 176 */ 177 public void setIntrinsicWidth(int width) { 178 mShapeState.mIntrinsicWidth = width; 179 invalidateSelf(); 180 } 181 182 /** 183 * Sets the intrinsic (default) height for this shape. 184 * 185 * @param height the intrinsic height (in pixels) 186 */ 187 public void setIntrinsicHeight(int height) { 188 mShapeState.mIntrinsicHeight = height; 189 invalidateSelf(); 190 } 191 192 @Override 193 public int getIntrinsicWidth() { 194 return mShapeState.mIntrinsicWidth; 195 } 196 197 @Override 198 public int getIntrinsicHeight() { 199 return mShapeState.mIntrinsicHeight; 200 } 201 202 @Override 203 public boolean getPadding(Rect padding) { 204 if (mShapeState.mPadding != null) { 205 padding.set(mShapeState.mPadding); 206 return true; 207 } else { 208 return super.getPadding(padding); 209 } 210 } 211 212 private static int modulateAlpha(int paintAlpha, int alpha) { 213 int scale = alpha + (alpha >>> 7); // convert to 0..256 214 return paintAlpha * scale >>> 8; 215 } 216 217 /** 218 * Called from the drawable's draw() method after the canvas has been set to 219 * draw the shape at (0,0). Subclasses can override for special effects such 220 * as multiple layers, stroking, etc. 221 */ 222 protected void onDraw(Shape shape, Canvas canvas, Paint paint) { 223 shape.draw(canvas, paint); 224 } 225 226 @Override 227 public void draw(Canvas canvas) { 228 final Rect r = getBounds(); 229 final ShapeState state = mShapeState; 230 final Paint paint = state.mPaint; 231 232 final int prevAlpha = paint.getAlpha(); 233 paint.setAlpha(modulateAlpha(prevAlpha, state.mAlpha)); 234 235 // only draw shape if it may affect output 236 if (paint.getAlpha() != 0 || paint.getXfermode() != null || paint.hasShadowLayer()) { 237 final boolean clearColorFilter; 238 if (mTintFilter != null && paint.getColorFilter() == null) { 239 paint.setColorFilter(mTintFilter); 240 clearColorFilter = true; 241 } else { 242 clearColorFilter = false; 243 } 244 245 if (state.mShape != null) { 246 // need the save both for the translate, and for the (unknown) 247 // Shape 248 final int count = canvas.save(); 249 canvas.translate(r.left, r.top); 250 onDraw(state.mShape, canvas, paint); 251 canvas.restoreToCount(count); 252 } else { 253 canvas.drawRect(r, paint); 254 } 255 256 if (clearColorFilter) { 257 paint.setColorFilter(null); 258 } 259 } 260 261 // restore 262 paint.setAlpha(prevAlpha); 263 } 264 265 @Override 266 public @Config int getChangingConfigurations() { 267 return super.getChangingConfigurations() | mShapeState.getChangingConfigurations(); 268 } 269 270 /** 271 * Set the alpha level for this drawable [0..255]. Note that this drawable 272 * also has a color in its paint, which has an alpha as well. These two 273 * values are automatically combined during drawing. Thus if the color's 274 * alpha is 75% (i.e. 192) and the drawable's alpha is 50% (i.e. 128), then 275 * the combined alpha that will be used during drawing will be 37.5% (i.e. 276 * 96). 277 */ 278 @Override 279 public void setAlpha(int alpha) { 280 mShapeState.mAlpha = alpha; 281 invalidateSelf(); 282 } 283 284 @Override 285 public int getAlpha() { 286 return mShapeState.mAlpha; 287 } 288 289 @Override 290 public void setTintList(ColorStateList tint) { 291 mShapeState.mTint = tint; 292 mTintFilter = updateTintFilter(mTintFilter, tint, mShapeState.mTintMode); 293 invalidateSelf(); 294 } 295 296 @Override 297 public void setTintMode(PorterDuff.Mode tintMode) { 298 mShapeState.mTintMode = tintMode; 299 mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, tintMode); 300 invalidateSelf(); 301 } 302 303 @Override 304 public void setColorFilter(ColorFilter colorFilter) { 305 mShapeState.mPaint.setColorFilter(colorFilter); 306 invalidateSelf(); 307 } 308 309 @Override 310 public int getOpacity() { 311 if (mShapeState.mShape == null) { 312 final Paint p = mShapeState.mPaint; 313 if (p.getXfermode() == null) { 314 final int alpha = p.getAlpha(); 315 if (alpha == 0) { 316 return PixelFormat.TRANSPARENT; 317 } 318 if (alpha == 255) { 319 return PixelFormat.OPAQUE; 320 } 321 } 322 } 323 // not sure, so be safe 324 return PixelFormat.TRANSLUCENT; 325 } 326 327 @Override 328 public void setDither(boolean dither) { 329 mShapeState.mPaint.setDither(dither); 330 invalidateSelf(); 331 } 332 333 @Override 334 protected void onBoundsChange(Rect bounds) { 335 super.onBoundsChange(bounds); 336 updateShape(); 337 } 338 339 @Override 340 protected boolean onStateChange(int[] stateSet) { 341 final ShapeState state = mShapeState; 342 if (state.mTint != null && state.mTintMode != null) { 343 mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); 344 return true; 345 } 346 return false; 347 } 348 349 @Override 350 public boolean isStateful() { 351 final ShapeState s = mShapeState; 352 return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); 353 } 354 355 /** @hide */ 356 @Override 357 public boolean hasFocusStateSpecified() { 358 return mShapeState.mTint != null && mShapeState.mTint.hasFocusStateSpecified(); 359 } 360 361 /** 362 * Subclasses override this to parse custom subelements. If you handle it, 363 * return true, else return <em>super.inflateTag(...)</em>. 364 */ 365 protected boolean inflateTag(String name, Resources r, XmlPullParser parser, 366 AttributeSet attrs) { 367 368 if ("padding".equals(name)) { 369 TypedArray a = r.obtainAttributes(attrs, 370 com.android.internal.R.styleable.ShapeDrawablePadding); 371 setPadding( 372 a.getDimensionPixelOffset( 373 com.android.internal.R.styleable.ShapeDrawablePadding_left, 0), 374 a.getDimensionPixelOffset( 375 com.android.internal.R.styleable.ShapeDrawablePadding_top, 0), 376 a.getDimensionPixelOffset( 377 com.android.internal.R.styleable.ShapeDrawablePadding_right, 0), 378 a.getDimensionPixelOffset( 379 com.android.internal.R.styleable.ShapeDrawablePadding_bottom, 0)); 380 a.recycle(); 381 return true; 382 } 383 384 return false; 385 } 386 387 @Override 388 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 389 throws XmlPullParserException, IOException { 390 super.inflate(r, parser, attrs, theme); 391 392 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ShapeDrawable); 393 updateStateFromTypedArray(a); 394 a.recycle(); 395 396 int type; 397 final int outerDepth = parser.getDepth(); 398 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 399 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 400 if (type != XmlPullParser.START_TAG) { 401 continue; 402 } 403 404 final String name = parser.getName(); 405 // call our subclass 406 if (!inflateTag(name, r, parser, attrs)) { 407 android.util.Log.w("drawable", "Unknown element: " + name + 408 " for ShapeDrawable " + this); 409 } 410 } 411 412 // Update local properties. 413 updateLocalState(); 414 } 415 416 @Override 417 public void applyTheme(Theme t) { 418 super.applyTheme(t); 419 420 final ShapeState state = mShapeState; 421 if (state == null) { 422 return; 423 } 424 425 if (state.mThemeAttrs != null) { 426 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ShapeDrawable); 427 updateStateFromTypedArray(a); 428 a.recycle(); 429 } 430 431 // Apply theme to contained color state list. 432 if (state.mTint != null && state.mTint.canApplyTheme()) { 433 state.mTint = state.mTint.obtainForTheme(t); 434 } 435 436 // Update local properties. 437 updateLocalState(); 438 } 439 440 private void updateStateFromTypedArray(TypedArray a) { 441 final ShapeState state = mShapeState; 442 final Paint paint = state.mPaint; 443 444 // Account for any configuration changes. 445 state.mChangingConfigurations |= a.getChangingConfigurations(); 446 447 // Extract the theme attributes, if any. 448 state.mThemeAttrs = a.extractThemeAttrs(); 449 450 int color = paint.getColor(); 451 color = a.getColor(R.styleable.ShapeDrawable_color, color); 452 paint.setColor(color); 453 454 boolean dither = paint.isDither(); 455 dither = a.getBoolean(R.styleable.ShapeDrawable_dither, dither); 456 paint.setDither(dither); 457 458 state.mIntrinsicWidth = (int) a.getDimension( 459 R.styleable.ShapeDrawable_width, state.mIntrinsicWidth); 460 state.mIntrinsicHeight = (int) a.getDimension( 461 R.styleable.ShapeDrawable_height, state.mIntrinsicHeight); 462 463 final int tintMode = a.getInt(R.styleable.ShapeDrawable_tintMode, -1); 464 if (tintMode != -1) { 465 state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); 466 } 467 468 final ColorStateList tint = a.getColorStateList(R.styleable.ShapeDrawable_tint); 469 if (tint != null) { 470 state.mTint = tint; 471 } 472 } 473 474 private void updateShape() { 475 if (mShapeState.mShape != null) { 476 final Rect r = getBounds(); 477 final int w = r.width(); 478 final int h = r.height(); 479 480 mShapeState.mShape.resize(w, h); 481 if (mShapeState.mShaderFactory != null) { 482 mShapeState.mPaint.setShader(mShapeState.mShaderFactory.resize(w, h)); 483 } 484 } 485 invalidateSelf(); 486 } 487 488 @Override 489 public void getOutline(Outline outline) { 490 if (mShapeState.mShape != null) { 491 mShapeState.mShape.getOutline(outline); 492 outline.setAlpha(getAlpha() / 255.0f); 493 } 494 } 495 496 @Override 497 public ConstantState getConstantState() { 498 mShapeState.mChangingConfigurations = getChangingConfigurations(); 499 return mShapeState; 500 } 501 502 @Override 503 public Drawable mutate() { 504 if (!mMutated && super.mutate() == this) { 505 mShapeState = new ShapeState(mShapeState); 506 updateLocalState(); 507 mMutated = true; 508 } 509 return this; 510 } 511 512 /** 513 * @hide 514 */ 515 public void clearMutated() { 516 super.clearMutated(); 517 mMutated = false; 518 } 519 520 /** 521 * Defines the intrinsic properties of this ShapeDrawable's Shape. 522 */ 523 static final class ShapeState extends ConstantState { 524 final @NonNull Paint mPaint; 525 526 @Config int mChangingConfigurations; 527 int[] mThemeAttrs; 528 Shape mShape; 529 ColorStateList mTint; 530 Mode mTintMode = DEFAULT_TINT_MODE; 531 Rect mPadding; 532 int mIntrinsicWidth; 533 int mIntrinsicHeight; 534 int mAlpha = 255; 535 ShaderFactory mShaderFactory; 536 537 /** 538 * Constructs a new ShapeState. 539 */ 540 ShapeState() { 541 mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 542 } 543 544 /** 545 * Constructs a new ShapeState that contains a deep copy of the 546 * specified ShapeState. 547 * 548 * @param orig the state to create a deep copy of 549 */ 550 ShapeState(@NonNull ShapeState orig) { 551 mChangingConfigurations = orig.mChangingConfigurations; 552 mPaint = new Paint(orig.mPaint); 553 mThemeAttrs = orig.mThemeAttrs; 554 if (orig.mShape != null) { 555 try { 556 mShape = orig.mShape.clone(); 557 } catch (CloneNotSupportedException e) { 558 // Well, at least we tried. 559 mShape = orig.mShape; 560 } 561 } 562 mTint = orig.mTint; 563 mTintMode = orig.mTintMode; 564 if (orig.mPadding != null) { 565 mPadding = new Rect(orig.mPadding); 566 } 567 mIntrinsicWidth = orig.mIntrinsicWidth; 568 mIntrinsicHeight = orig.mIntrinsicHeight; 569 mAlpha = orig.mAlpha; 570 571 // We don't have any way to clone a shader factory, so hopefully 572 // this class doesn't contain any local state. 573 mShaderFactory = orig.mShaderFactory; 574 } 575 576 @Override 577 public boolean canApplyTheme() { 578 return mThemeAttrs != null 579 || (mTint != null && mTint.canApplyTheme()); 580 } 581 582 @Override 583 public Drawable newDrawable() { 584 return new ShapeDrawable(this, null); 585 } 586 587 @Override 588 public Drawable newDrawable(Resources res) { 589 return new ShapeDrawable(this, res); 590 } 591 592 @Override 593 public @Config int getChangingConfigurations() { 594 return mChangingConfigurations 595 | (mTint != null ? mTint.getChangingConfigurations() : 0); 596 } 597 } 598 599 /** 600 * The one constructor to rule them all. This is called by all public 601 * constructors to set the state and initialize local properties. 602 */ 603 private ShapeDrawable(ShapeState state, Resources res) { 604 mShapeState = state; 605 606 updateLocalState(); 607 } 608 609 /** 610 * Initializes local dynamic properties from state. This should be called 611 * after significant state changes, e.g. from the One True Constructor and 612 * after inflating or applying a theme. 613 */ 614 private void updateLocalState() { 615 mTintFilter = updateTintFilter(mTintFilter, mShapeState.mTint, mShapeState.mTintMode); 616 } 617 618 /** 619 * Base class defines a factory object that is called each time the drawable 620 * is resized (has a new width or height). Its resize() method returns a 621 * corresponding shader, or null. Implement this class if you'd like your 622 * ShapeDrawable to use a special {@link android.graphics.Shader}, such as a 623 * {@link android.graphics.LinearGradient}. 624 */ 625 public static abstract class ShaderFactory { 626 /** 627 * Returns the Shader to be drawn when a Drawable is drawn. The 628 * dimensions of the Drawable are passed because they may be needed to 629 * adjust how the Shader is configured for drawing. This is called by 630 * ShapeDrawable.setShape(). 631 * 632 * @param width the width of the Drawable being drawn 633 * @param height the heigh of the Drawable being drawn 634 * @return the Shader to be drawn 635 */ 636 public abstract Shader resize(int width, int height); 637 } 638} 639