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