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