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