NinePatchDrawable.java revision 2f8ba8f7fad2b608102a9282219aaea2223e94f5
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 17package android.graphics.drawable; 18 19import android.content.res.ColorStateList; 20import android.content.res.Resources; 21import android.content.res.Resources.Theme; 22import android.content.res.TypedArray; 23import android.graphics.Bitmap; 24import android.graphics.BitmapFactory; 25import android.graphics.Canvas; 26import android.graphics.ColorFilter; 27import android.graphics.Insets; 28import android.graphics.NinePatch; 29import android.graphics.Paint; 30import android.graphics.PixelFormat; 31import android.graphics.PorterDuff.Mode; 32import android.graphics.PorterDuffColorFilter; 33import android.graphics.Rect; 34import android.graphics.Region; 35import android.util.AttributeSet; 36import android.util.DisplayMetrics; 37import android.util.LayoutDirection; 38import android.util.TypedValue; 39 40import com.android.internal.R; 41 42import org.xmlpull.v1.XmlPullParser; 43import org.xmlpull.v1.XmlPullParserException; 44 45import java.io.IOException; 46import java.io.InputStream; 47 48/** 49 * 50 * A resizeable bitmap, with stretchable areas that you define. This type of image 51 * is defined in a .png file with a special format. 52 * 53 * <div class="special reference"> 54 * <h3>Developer Guides</h3> 55 * <p>For more information about how to use a NinePatchDrawable, read the 56 * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch"> 57 * Canvas and Drawables</a> developer guide. For information about creating a NinePatch image 58 * file using the draw9patch tool, see the 59 * <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div> 60 */ 61public class NinePatchDrawable extends Drawable { 62 // dithering helps a lot, and is pretty cheap, so default is true 63 private static final boolean DEFAULT_DITHER = false; 64 private NinePatchState mNinePatchState; 65 private NinePatch mNinePatch; 66 private PorterDuffColorFilter mTintFilter; 67 private Rect mPadding; 68 private Insets mOpticalInsets = Insets.NONE; 69 private Paint mPaint; 70 private boolean mMutated; 71 72 private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 73 74 // These are scaled to match the target density. 75 private int mBitmapWidth; 76 private int mBitmapHeight; 77 78 NinePatchDrawable() { 79 mNinePatchState = new NinePatchState(); 80 } 81 82 /** 83 * Create drawable from raw nine-patch data, not dealing with density. 84 * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)} 85 * to ensure that the drawable has correctly set its target density. 86 */ 87 @Deprecated 88 public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { 89 this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null, null); 90 } 91 92 /** 93 * Create drawable from raw nine-patch data, setting initial target density 94 * based on the display metrics of the resources. 95 */ 96 public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, 97 Rect padding, String srcName) { 98 this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res, null); 99 mNinePatchState.mTargetDensity = mTargetDensity; 100 } 101 102 /** 103 * Create drawable from raw nine-patch data, setting initial target density 104 * based on the display metrics of the resources. 105 * 106 * @hide 107 */ 108 public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, 109 Rect padding, Rect opticalInsets, String srcName) { 110 this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets), 111 res, null); 112 mNinePatchState.mTargetDensity = mTargetDensity; 113 } 114 115 /** 116 * Create drawable from existing nine-patch, not dealing with density. 117 * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)} 118 * to ensure that the drawable has correctly set its target density. 119 */ 120 @Deprecated 121 public NinePatchDrawable(NinePatch patch) { 122 this(new NinePatchState(patch, new Rect()), null, null); 123 } 124 125 /** 126 * Create drawable from existing nine-patch, setting initial target density 127 * based on the display metrics of the resources. 128 */ 129 public NinePatchDrawable(Resources res, NinePatch patch) { 130 this(new NinePatchState(patch, new Rect()), res, null); 131 mNinePatchState.mTargetDensity = mTargetDensity; 132 } 133 134 /** 135 * Set the density scale at which this drawable will be rendered. This 136 * method assumes the drawable will be rendered at the same density as the 137 * specified canvas. 138 * 139 * @param canvas The Canvas from which the density scale must be obtained. 140 * 141 * @see android.graphics.Bitmap#setDensity(int) 142 * @see android.graphics.Bitmap#getDensity() 143 */ 144 public void setTargetDensity(Canvas canvas) { 145 setTargetDensity(canvas.getDensity()); 146 } 147 148 /** 149 * Set the density scale at which this drawable will be rendered. 150 * 151 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 152 * 153 * @see android.graphics.Bitmap#setDensity(int) 154 * @see android.graphics.Bitmap#getDensity() 155 */ 156 public void setTargetDensity(DisplayMetrics metrics) { 157 setTargetDensity(metrics.densityDpi); 158 } 159 160 /** 161 * Set the density at which this drawable will be rendered. 162 * 163 * @param density The density scale for this drawable. 164 * 165 * @see android.graphics.Bitmap#setDensity(int) 166 * @see android.graphics.Bitmap#getDensity() 167 */ 168 public void setTargetDensity(int density) { 169 if (density != mTargetDensity) { 170 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 171 if (mNinePatch != null) { 172 computeBitmapSize(); 173 } 174 invalidateSelf(); 175 } 176 } 177 178 private static Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) { 179 int left = Bitmap.scaleFromDensity(insets.left, sdensity, tdensity); 180 int top = Bitmap.scaleFromDensity(insets.top, sdensity, tdensity); 181 int right = Bitmap.scaleFromDensity(insets.right, sdensity, tdensity); 182 int bottom = Bitmap.scaleFromDensity(insets.bottom, sdensity, tdensity); 183 return Insets.of(left, top, right, bottom); 184 } 185 186 private void computeBitmapSize() { 187 final int sdensity = mNinePatch.getDensity(); 188 final int tdensity = mTargetDensity; 189 if (sdensity == tdensity) { 190 mBitmapWidth = mNinePatch.getWidth(); 191 mBitmapHeight = mNinePatch.getHeight(); 192 mOpticalInsets = mNinePatchState.mOpticalInsets; 193 } else { 194 mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(), sdensity, tdensity); 195 mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(), sdensity, tdensity); 196 if (mNinePatchState.mPadding != null && mPadding != null) { 197 Rect dest = mPadding; 198 Rect src = mNinePatchState.mPadding; 199 if (dest == src) { 200 mPadding = dest = new Rect(src); 201 } 202 dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity); 203 dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity); 204 dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity); 205 dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity); 206 } 207 mOpticalInsets = scaleFromDensity(mNinePatchState.mOpticalInsets, sdensity, tdensity); 208 } 209 } 210 211 private void setNinePatch(NinePatch ninePatch) { 212 if (ninePatch != mNinePatch) { 213 mNinePatch = ninePatch; 214 if (ninePatch != null) { 215 computeBitmapSize(); 216 } else { 217 mBitmapWidth = mBitmapHeight = -1; 218 mOpticalInsets = Insets.NONE; 219 } 220 invalidateSelf(); 221 } 222 } 223 224 @Override 225 public void draw(Canvas canvas) { 226 final Rect bounds = getBounds(); 227 228 final boolean clearColorFilter; 229 if (mTintFilter != null && getPaint().getColorFilter() == null) { 230 mPaint.setColorFilter(mTintFilter); 231 clearColorFilter = true; 232 } else { 233 clearColorFilter = false; 234 } 235 236 final boolean needsMirroring = needsMirroring(); 237 if (needsMirroring) { 238 canvas.save(); 239 // Mirror the 9patch 240 canvas.translate(bounds.right - bounds.left, 0); 241 canvas.scale(-1.0f, 1.0f); 242 } 243 244 mNinePatch.draw(canvas, bounds, mPaint); 245 246 if (needsMirroring) { 247 canvas.restore(); 248 } 249 250 if (clearColorFilter) { 251 mPaint.setColorFilter(null); 252 } 253 } 254 255 @Override 256 public int getChangingConfigurations() { 257 return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations; 258 } 259 260 @Override 261 public boolean getPadding(Rect padding) { 262 final Rect scaledPadding = mPadding; 263 if (scaledPadding != null) { 264 if (needsMirroring()) { 265 padding.set(scaledPadding.right, scaledPadding.top, 266 scaledPadding.left, scaledPadding.bottom); 267 } else { 268 padding.set(scaledPadding); 269 } 270 return (padding.left | padding.top | padding.right | padding.bottom) != 0; 271 } 272 return false; 273 } 274 275 /** 276 * @hide 277 */ 278 @Override 279 public Insets getOpticalInsets() { 280 if (needsMirroring()) { 281 return Insets.of(mOpticalInsets.right, mOpticalInsets.top, mOpticalInsets.right, 282 mOpticalInsets.bottom); 283 } else { 284 return mOpticalInsets; 285 } 286 } 287 288 @Override 289 public void setAlpha(int alpha) { 290 if (mPaint == null && alpha == 0xFF) { 291 // Fast common case -- leave at normal alpha. 292 return; 293 } 294 getPaint().setAlpha(alpha); 295 invalidateSelf(); 296 } 297 298 @Override 299 public int getAlpha() { 300 if (mPaint == null) { 301 // Fast common case -- normal alpha. 302 return 0xFF; 303 } 304 return getPaint().getAlpha(); 305 } 306 307 @Override 308 public void setColorFilter(ColorFilter cf) { 309 if (mPaint == null && cf == null) { 310 // Fast common case -- leave at no color filter. 311 return; 312 } 313 getPaint().setColorFilter(cf); 314 invalidateSelf(); 315 } 316 317 /** 318 * Specifies a tint for this drawable. 319 * <p> 320 * Setting a color filter via {@link #setColorFilter(ColorFilter)} overrides 321 * tint. 322 * 323 * @param tint Color state list to use for tinting this drawable, or null to 324 * clear the tint 325 */ 326 public void setTint(ColorStateList tint) { 327 if (mNinePatchState.mTint != tint) { 328 mNinePatchState.mTint = tint; 329 updateTintFilter(); 330 invalidateSelf(); 331 } 332 } 333 334 /** 335 * Returns the tint color for this drawable. 336 * 337 * @return Color state list to use for tinting this drawable, or null if 338 * none set 339 */ 340 public ColorStateList getTint() { 341 return mNinePatchState.mTint; 342 } 343 344 /** 345 * Specifies the blending mode used to apply tint. 346 * 347 * @param tintMode A Porter-Duff blending mode 348 */ 349 public void setTintMode(Mode tintMode) { 350 if (mNinePatchState.mTintMode != tintMode) { 351 mNinePatchState.mTintMode = tintMode; 352 updateTintFilter(); 353 invalidateSelf(); 354 } 355 } 356 357 /** 358 * Ensures the tint filter is consistent with the current tint color and 359 * mode. 360 */ 361 private void updateTintFilter() { 362 final ColorStateList tint = mNinePatchState.mTint; 363 final Mode tintMode = mNinePatchState.mTintMode; 364 if (tint != null && tintMode != null) { 365 if (mTintFilter == null) { 366 mTintFilter = new PorterDuffColorFilter(0, tintMode); 367 } else { 368 mTintFilter.setMode(tintMode); 369 } 370 } else { 371 mTintFilter = null; 372 } 373 } 374 375 @Override 376 public void setDither(boolean dither) { 377 //noinspection PointlessBooleanExpression 378 if (mPaint == null && dither == DEFAULT_DITHER) { 379 // Fast common case -- leave at default dither. 380 return; 381 } 382 383 getPaint().setDither(dither); 384 invalidateSelf(); 385 } 386 387 @Override 388 public void setAutoMirrored(boolean mirrored) { 389 mNinePatchState.mAutoMirrored = mirrored; 390 } 391 392 private boolean needsMirroring() { 393 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 394 } 395 396 @Override 397 public boolean isAutoMirrored() { 398 return mNinePatchState.mAutoMirrored; 399 } 400 401 @Override 402 public void setFilterBitmap(boolean filter) { 403 getPaint().setFilterBitmap(filter); 404 invalidateSelf(); 405 } 406 407 @Override 408 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 409 throws XmlPullParserException, IOException { 410 super.inflate(r, parser, attrs, theme); 411 412 final TypedArray a = obtainAttributes( 413 r, theme, attrs, R.styleable.NinePatchDrawable); 414 inflateStateFromTypedArray(a); 415 a.recycle(); 416 } 417 418 /** 419 * Initializes the constant state from the values in the typed array. 420 */ 421 private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException { 422 final Resources r = a.getResources(); 423 final NinePatchState ninePatchState = mNinePatchState; 424 425 // Extract the theme attributes, if any. 426 final int[] themeAttrs = a.extractThemeAttrs(); 427 ninePatchState.mThemeAttrs = themeAttrs; 428 429 if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_dither] == 0) { 430 final boolean dither = a.getBoolean( 431 R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); 432 ninePatchState.mDither = dither; 433 } 434 435 if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_src] == 0) { 436 final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); 437 if (id == 0) { 438 throw new XmlPullParserException(a.getPositionDescription() + 439 ": <nine-patch> requires a valid src attribute"); 440 } 441 442 final BitmapFactory.Options options = new BitmapFactory.Options(); 443 options.inDither = !ninePatchState.mDither; 444 options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; 445 446 final Rect padding = new Rect(); 447 final Rect opticalInsets = new Rect(); 448 Bitmap bitmap = null; 449 450 try { 451 final TypedValue value = new TypedValue(); 452 final InputStream is = r.openRawResource(id, value); 453 454 bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); 455 456 is.close(); 457 } catch (IOException e) { 458 // Ignore 459 } 460 461 if (bitmap == null) { 462 throw new XmlPullParserException(a.getPositionDescription() + 463 ": <nine-patch> requires a valid src attribute"); 464 } else if (bitmap.getNinePatchChunk() == null) { 465 throw new XmlPullParserException(a.getPositionDescription() + 466 ": <nine-patch> requires a valid 9-patch source image"); 467 } 468 469 final NinePatch ninePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); 470 ninePatchState.mNinePatch = ninePatch; 471 ninePatchState.mPadding = padding; 472 ninePatchState.mOpticalInsets = Insets.of(opticalInsets); 473 } 474 475 if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_autoMirrored] == 0) { 476 final boolean autoMirrored = a.getBoolean( 477 R.styleable.NinePatchDrawable_autoMirrored, false); 478 ninePatchState.mAutoMirrored = autoMirrored; 479 } 480 481 if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tintMode] == 0) { 482 final int tintModeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); 483 ninePatchState.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); 484 } 485 486 if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tint] == 0) { 487 ninePatchState.mTint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); 488 if (ninePatchState.mTint != null) { 489 final int color = ninePatchState.mTint.getColorForState(getState(), 0); 490 mTintFilter = new PorterDuffColorFilter(color, ninePatchState.mTintMode); 491 } 492 } 493 494 // Apply the constant state to the paint. 495 initializeWithState(ninePatchState, r); 496 497 // Push density applied by setNinePatchState into state. 498 ninePatchState.mTargetDensity = mTargetDensity; 499 } 500 501 @Override 502 public void applyTheme(Theme t) { 503 super.applyTheme(t); 504 505 final NinePatchState state = mNinePatchState; 506 if (state == null) { 507 throw new RuntimeException("Can't apply theme to <nine-patch> with no constant state"); 508 } 509 510 final int[] themeAttrs = state.mThemeAttrs; 511 if (themeAttrs != null) { 512 final TypedArray a = t.resolveAttributes( 513 themeAttrs, R.styleable.NinePatchDrawable, 0, 0); 514 updateStateFromTypedArray(a); 515 a.recycle(); 516 } 517 } 518 519 /** 520 * Updates the constant state from the values in the typed array. 521 */ 522 private void updateStateFromTypedArray(TypedArray a) { 523 final Resources r = a.getResources(); 524 final NinePatchState state = mNinePatchState; 525 526 if (a.hasValue(R.styleable.NinePatchDrawable_dither)) { 527 state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); 528 } 529 530 if (a.hasValue(R.styleable.NinePatchDrawable_autoMirrored)) { 531 state.mAutoMirrored = a.getBoolean(R.styleable.NinePatchDrawable_autoMirrored, false); 532 } 533 534 if (a.hasValue(R.styleable.NinePatchDrawable_src)) { 535 final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); 536 if (id == 0) { 537 throw new RuntimeException(a.getPositionDescription() + 538 ": <nine-patch> requires a valid src attribute"); 539 } 540 541 final BitmapFactory.Options options = new BitmapFactory.Options(); 542 options.inDither = !state.mDither; 543 options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; 544 545 final Rect padding = new Rect(); 546 final Rect opticalInsets = new Rect(); 547 Bitmap bitmap = null; 548 549 try { 550 final TypedValue value = new TypedValue(); 551 final InputStream is = r.openRawResource(id, value); 552 553 bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); 554 555 is.close(); 556 } catch (IOException e) { 557 // Ignore 558 } 559 560 if (bitmap == null) { 561 throw new RuntimeException(a.getPositionDescription() + 562 ": <nine-patch> requires a valid src attribute"); 563 } else if (bitmap.getNinePatchChunk() == null) { 564 throw new RuntimeException(a.getPositionDescription() + 565 ": <nine-patch> requires a valid 9-patch source image"); 566 } 567 568 state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); 569 state.mPadding = padding; 570 state.mOpticalInsets = Insets.of(opticalInsets); 571 } 572 573 if (a.hasValue(R.styleable.NinePatchDrawable_tintMode)) { 574 final int modeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); 575 state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN); 576 } 577 578 if (a.hasValue(R.styleable.NinePatchDrawable_tint)) { 579 final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); 580 if (tint != null) { 581 state.mTint = tint; 582 final int color = tint.getColorForState(getState(), 0); 583 mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); 584 } 585 } 586 587 // Apply the constant state to the paint. 588 initializeWithState(state, r); 589 590 // Push density applied by setNinePatchState into state. 591 state.mTargetDensity = mTargetDensity; 592 } 593 594 @Override 595 public boolean canApplyTheme() { 596 return mNinePatchState != null && mNinePatchState.mThemeAttrs != null; 597 } 598 599 public Paint getPaint() { 600 if (mPaint == null) { 601 mPaint = new Paint(); 602 mPaint.setDither(DEFAULT_DITHER); 603 } 604 return mPaint; 605 } 606 607 /** 608 * Retrieves the width of the source .png file (before resizing). 609 */ 610 @Override 611 public int getIntrinsicWidth() { 612 return mBitmapWidth; 613 } 614 615 /** 616 * Retrieves the height of the source .png file (before resizing). 617 */ 618 @Override 619 public int getIntrinsicHeight() { 620 return mBitmapHeight; 621 } 622 623 @Override 624 public int getMinimumWidth() { 625 return mBitmapWidth; 626 } 627 628 @Override 629 public int getMinimumHeight() { 630 return mBitmapHeight; 631 } 632 633 /** 634 * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat} 635 * value of OPAQUE or TRANSLUCENT. 636 */ 637 @Override 638 public int getOpacity() { 639 return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ? 640 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 641 } 642 643 @Override 644 public Region getTransparentRegion() { 645 return mNinePatch.getTransparentRegion(getBounds()); 646 } 647 648 @Override 649 public ConstantState getConstantState() { 650 mNinePatchState.mChangingConfigurations = getChangingConfigurations(); 651 return mNinePatchState; 652 } 653 654 @Override 655 public Drawable mutate() { 656 if (!mMutated && super.mutate() == this) { 657 mNinePatchState = new NinePatchState(mNinePatchState); 658 mNinePatch = mNinePatchState.mNinePatch; 659 mMutated = true; 660 } 661 return this; 662 } 663 664 @Override 665 protected boolean onStateChange(int[] stateSet) { 666 final ColorStateList tint = mNinePatchState.mTint; 667 if (tint != null) { 668 final int newColor = tint.getColorForState(stateSet, 0); 669 final int oldColor = mTintFilter.getColor(); 670 if (oldColor != newColor) { 671 mTintFilter.setColor(newColor); 672 invalidateSelf(); 673 return true; 674 } 675 } 676 677 return false; 678 } 679 680 @Override 681 public boolean isStateful() { 682 final NinePatchState s = mNinePatchState; 683 return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); 684 } 685 686 final static class NinePatchState extends ConstantState { 687 NinePatch mNinePatch; 688 ColorStateList mTint; 689 Mode mTintMode = Mode.SRC_IN; 690 Rect mPadding; 691 Insets mOpticalInsets; 692 boolean mDither; 693 int[] mThemeAttrs; 694 int mChangingConfigurations; 695 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 696 boolean mAutoMirrored; 697 698 NinePatchState() { 699 // Empty constructor. 700 } 701 702 NinePatchState(NinePatch ninePatch, Rect padding) { 703 this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false); 704 } 705 706 NinePatchState(NinePatch ninePatch, Rect padding, Rect opticalInsets) { 707 this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false); 708 } 709 710 NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither, 711 boolean autoMirror) { 712 mNinePatch = ninePatch; 713 mPadding = rect; 714 mOpticalInsets = Insets.of(opticalInsets); 715 mDither = dither; 716 mAutoMirrored = autoMirror; 717 } 718 719 // Copy constructor 720 721 NinePatchState(NinePatchState state) { 722 // We don't deep-copy any fields because they are all immutable. 723 mNinePatch = state.mNinePatch; 724 mTint = state.mTint; 725 mTintMode = state.mTintMode; 726 mThemeAttrs = state.mThemeAttrs; 727 mPadding = state.mPadding; 728 mOpticalInsets = state.mOpticalInsets; 729 mDither = state.mDither; 730 mChangingConfigurations = state.mChangingConfigurations; 731 mTargetDensity = state.mTargetDensity; 732 mAutoMirrored = state.mAutoMirrored; 733 } 734 735 @Override 736 public boolean canApplyTheme() { 737 return mThemeAttrs != null; 738 } 739 740 @Override 741 public Bitmap getBitmap() { 742 return mNinePatch.getBitmap(); 743 } 744 745 @Override 746 public Drawable newDrawable() { 747 return new NinePatchDrawable(this, null, null); 748 } 749 750 @Override 751 public Drawable newDrawable(Resources res) { 752 return new NinePatchDrawable(this, res, null); 753 } 754 755 @Override 756 public Drawable newDrawable(Resources res, Theme theme) { 757 return new NinePatchDrawable(this, res, theme); 758 } 759 760 @Override 761 public int getChangingConfigurations() { 762 return mChangingConfigurations; 763 } 764 } 765 766 private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) { 767 if (theme != null && state.canApplyTheme()) { 768 mNinePatchState = new NinePatchState(state); 769 applyTheme(theme); 770 } else { 771 mNinePatchState = state; 772 } 773 774 initializeWithState(state, res); 775 } 776 777 /** 778 * Initializes local dynamic properties from state. 779 */ 780 private void initializeWithState(NinePatchState state, Resources res) { 781 if (res != null) { 782 mTargetDensity = res.getDisplayMetrics().densityDpi; 783 } else { 784 mTargetDensity = state.mTargetDensity; 785 } 786 787 // If we can, avoid calling any methods that initialize Paint. 788 if (state.mDither != DEFAULT_DITHER) { 789 setDither(state.mDither); 790 } 791 792 if (state.mTint != null) { 793 final int color = state.mTint.getColorForState(getState(), 0); 794 mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); 795 } 796 797 final Rect statePadding = state.mPadding; 798 mPadding = statePadding != null ? new Rect(statePadding) : null; 799 800 setNinePatch(state.mNinePatch); 801 } 802} 803