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