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