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