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.compat.annotation.UnsupportedAppUsage; 22 import android.content.pm.ActivityInfo.Config; 23 import android.content.res.ColorStateList; 24 import android.content.res.Resources; 25 import android.content.res.Resources.Theme; 26 import android.content.res.TypedArray; 27 import android.graphics.Bitmap; 28 import android.graphics.BlendMode; 29 import android.graphics.BlendModeColorFilter; 30 import android.graphics.Canvas; 31 import android.graphics.ColorFilter; 32 import android.graphics.ImageDecoder; 33 import android.graphics.Insets; 34 import android.graphics.NinePatch; 35 import android.graphics.Outline; 36 import android.graphics.Paint; 37 import android.graphics.PixelFormat; 38 import android.graphics.Rect; 39 import android.graphics.Region; 40 import android.util.AttributeSet; 41 import android.util.DisplayMetrics; 42 import android.util.LayoutDirection; 43 import android.util.TypedValue; 44 45 import com.android.internal.R; 46 47 import org.xmlpull.v1.XmlPullParser; 48 import org.xmlpull.v1.XmlPullParserException; 49 50 import java.io.IOException; 51 import 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 */ 66 public 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 @UnsupportedAppUsage 74 private NinePatchState mNinePatchState; 75 private BlendModeColorFilter mBlendModeFilter; 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 NinePatchDrawable()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 NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName)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 */ NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName)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 */ NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, Rect opticalInsets, String srcName)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 NinePatchDrawable(@onNull NinePatch patch)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 */ NinePatchDrawable(@ullable Resources res, @NonNull NinePatch patch)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 */ setTargetDensity(@onNull Canvas canvas)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 */ setTargetDensity(@onNull DisplayMetrics metrics)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 */ setTargetDensity(int density)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 draw(Canvas canvas)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 (mBlendModeFilter != null && getPaint().getColorFilter() == null) { 201 mPaint.setColorFilter(mBlendModeFilter); 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 && Bitmap.DENSITY_NONE != state.mNinePatch.getDensity(); 217 if (needsDensityScaling) { 218 restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save(); 219 220 // Apply density scaling. 221 final float scale = mTargetDensity / (float) state.mNinePatch.getDensity(); 222 final float px = bounds.left; 223 final float py = bounds.top; 224 canvas.scale(scale, scale, px, py); 225 226 if (mTempRect == null) { 227 mTempRect = new Rect(); 228 } 229 230 // Scale the bounds to match. 231 final Rect scaledBounds = mTempRect; 232 scaledBounds.left = bounds.left; 233 scaledBounds.top = bounds.top; 234 scaledBounds.right = bounds.left + Math.round(bounds.width() / scale); 235 scaledBounds.bottom = bounds.top + Math.round(bounds.height() / scale); 236 bounds = scaledBounds; 237 } 238 239 final boolean needsMirroring = needsMirroring(); 240 if (needsMirroring) { 241 restoreToCount = restoreToCount >= 0 ? restoreToCount : canvas.save(); 242 243 // Mirror the 9patch. 244 final float cx = (bounds.left + bounds.right) / 2.0f; 245 final float cy = (bounds.top + bounds.bottom) / 2.0f; 246 canvas.scale(-1.0f, 1.0f, cx, cy); 247 } 248 249 state.mNinePatch.draw(canvas, bounds, mPaint); 250 251 if (restoreToCount >= 0) { 252 canvas.restoreToCount(restoreToCount); 253 } 254 255 if (clearColorFilter) { 256 mPaint.setColorFilter(null); 257 } 258 259 if (restoreAlpha >= 0) { 260 mPaint.setAlpha(restoreAlpha); 261 } 262 } 263 264 @Override getChangingConfigurations()265 public @Config int getChangingConfigurations() { 266 return super.getChangingConfigurations() | mNinePatchState.getChangingConfigurations(); 267 } 268 269 @Override getPadding(@onNull Rect padding)270 public boolean getPadding(@NonNull Rect padding) { 271 if (mPadding != null) { 272 padding.set(mPadding); 273 return (padding.left | padding.top | padding.right | padding.bottom) != 0; 274 } else { 275 return super.getPadding(padding); 276 } 277 } 278 279 @Override getOutline(@onNull Outline outline)280 public void getOutline(@NonNull Outline outline) { 281 final Rect bounds = getBounds(); 282 if (bounds.isEmpty()) { 283 return; 284 } 285 286 if (mNinePatchState != null && mOutlineInsets != null) { 287 final NinePatch.InsetStruct insets = 288 mNinePatchState.mNinePatch.getBitmap().getNinePatchInsets(); 289 if (insets != null) { 290 outline.setRoundRect(bounds.left + mOutlineInsets.left, 291 bounds.top + mOutlineInsets.top, 292 bounds.right - mOutlineInsets.right, 293 bounds.bottom - mOutlineInsets.bottom, 294 mOutlineRadius); 295 outline.setAlpha(insets.outlineAlpha * (getAlpha() / 255.0f)); 296 return; 297 } 298 } 299 300 super.getOutline(outline); 301 } 302 303 @Override getOpticalInsets()304 public Insets getOpticalInsets() { 305 final Insets opticalInsets = mOpticalInsets; 306 if (needsMirroring()) { 307 return Insets.of(opticalInsets.right, opticalInsets.top, 308 opticalInsets.left, opticalInsets.bottom); 309 } else { 310 return opticalInsets; 311 } 312 } 313 314 @Override setAlpha(int alpha)315 public void setAlpha(int alpha) { 316 if (mPaint == null && alpha == 0xFF) { 317 // Fast common case -- leave at normal alpha. 318 return; 319 } 320 getPaint().setAlpha(alpha); 321 invalidateSelf(); 322 } 323 324 @Override getAlpha()325 public int getAlpha() { 326 if (mPaint == null) { 327 // Fast common case -- normal alpha. 328 return 0xFF; 329 } 330 return getPaint().getAlpha(); 331 } 332 333 @Override setColorFilter(@ullable ColorFilter colorFilter)334 public void setColorFilter(@Nullable ColorFilter colorFilter) { 335 if (mPaint == null && colorFilter == null) { 336 // Fast common case -- leave at no color filter. 337 return; 338 } 339 getPaint().setColorFilter(colorFilter); 340 invalidateSelf(); 341 } 342 343 @Override setTintList(@ullable ColorStateList tint)344 public void setTintList(@Nullable ColorStateList tint) { 345 mNinePatchState.mTint = tint; 346 mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint, 347 mNinePatchState.mBlendMode); 348 invalidateSelf(); 349 } 350 351 @Override setTintBlendMode(@ullable BlendMode blendMode)352 public void setTintBlendMode(@Nullable BlendMode blendMode) { 353 mNinePatchState.mBlendMode = blendMode; 354 mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mNinePatchState.mTint, 355 blendMode); 356 invalidateSelf(); 357 } 358 359 @Override setDither(boolean dither)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 setAutoMirrored(boolean mirrored)372 public void setAutoMirrored(boolean mirrored) { 373 mNinePatchState.mAutoMirrored = mirrored; 374 } 375 needsMirroring()376 private boolean needsMirroring() { 377 return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; 378 } 379 380 @Override isAutoMirrored()381 public boolean isAutoMirrored() { 382 return mNinePatchState.mAutoMirrored; 383 } 384 385 @Override setFilterBitmap(boolean filter)386 public void setFilterBitmap(boolean filter) { 387 getPaint().setFilterBitmap(filter); 388 invalidateSelf(); 389 } 390 391 @Override isFilterBitmap()392 public boolean isFilterBitmap() { 393 return mPaint != null && getPaint().isFilterBitmap(); 394 } 395 396 @Override inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)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 */ updateStateFromTypedArray(@onNull TypedArray a)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 Rect padding = new Rect(); 426 final Rect opticalInsets = new Rect(); 427 Bitmap bitmap = null; 428 429 try { 430 final TypedValue value = new TypedValue(); 431 final InputStream is = r.openRawResource(srcResId, value); 432 433 int density = Bitmap.DENSITY_NONE; 434 if (value.density == TypedValue.DENSITY_DEFAULT) { 435 density = DisplayMetrics.DENSITY_DEFAULT; 436 } else if (value.density != TypedValue.DENSITY_NONE) { 437 density = value.density; 438 } 439 ImageDecoder.Source source = ImageDecoder.createSource(r, is, density); 440 bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> { 441 decoder.setOutPaddingRect(padding); 442 decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); 443 }); 444 445 is.close(); 446 } catch (IOException e) { 447 // Ignore 448 } 449 450 if (bitmap == null) { 451 throw new XmlPullParserException(a.getPositionDescription() + 452 ": <nine-patch> requires a valid src attribute"); 453 } else if (bitmap.getNinePatchChunk() == null) { 454 throw new XmlPullParserException(a.getPositionDescription() + 455 ": <nine-patch> requires a valid 9-patch source image"); 456 } 457 458 bitmap.getOpticalInsets(opticalInsets); 459 460 state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); 461 state.mPadding = padding; 462 state.mOpticalInsets = Insets.of(opticalInsets); 463 } 464 465 state.mAutoMirrored = a.getBoolean( 466 R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored); 467 state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha); 468 469 final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); 470 if (tintMode != -1) { 471 state.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.SRC_IN); 472 } 473 474 final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); 475 if (tint != null) { 476 state.mTint = tint; 477 } 478 } 479 480 @Override applyTheme(@onNull Theme t)481 public void applyTheme(@NonNull Theme t) { 482 super.applyTheme(t); 483 484 final NinePatchState state = mNinePatchState; 485 if (state == null) { 486 return; 487 } 488 489 if (state.mThemeAttrs != null) { 490 final TypedArray a = t.resolveAttributes( 491 state.mThemeAttrs, R.styleable.NinePatchDrawable); 492 try { 493 updateStateFromTypedArray(a); 494 } catch (XmlPullParserException e) { 495 rethrowAsRuntimeException(e); 496 } finally { 497 a.recycle(); 498 } 499 } 500 501 if (state.mTint != null && state.mTint.canApplyTheme()) { 502 state.mTint = state.mTint.obtainForTheme(t); 503 } 504 505 updateLocalState(t.getResources()); 506 } 507 508 @Override canApplyTheme()509 public boolean canApplyTheme() { 510 return mNinePatchState != null && mNinePatchState.canApplyTheme(); 511 } 512 513 @NonNull getPaint()514 public Paint getPaint() { 515 if (mPaint == null) { 516 mPaint = new Paint(); 517 mPaint.setDither(DEFAULT_DITHER); 518 } 519 return mPaint; 520 } 521 522 @Override getIntrinsicWidth()523 public int getIntrinsicWidth() { 524 return mBitmapWidth; 525 } 526 527 @Override getIntrinsicHeight()528 public int getIntrinsicHeight() { 529 return mBitmapHeight; 530 } 531 532 @Override getOpacity()533 public int getOpacity() { 534 return mNinePatchState.mNinePatch.hasAlpha() 535 || (mPaint != null && mPaint.getAlpha() < 255) ? 536 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 537 } 538 539 @Override getTransparentRegion()540 public Region getTransparentRegion() { 541 return mNinePatchState.mNinePatch.getTransparentRegion(getBounds()); 542 } 543 544 @Override getConstantState()545 public ConstantState getConstantState() { 546 mNinePatchState.mChangingConfigurations = getChangingConfigurations(); 547 return mNinePatchState; 548 } 549 550 @Override mutate()551 public Drawable mutate() { 552 if (!mMutated && super.mutate() == this) { 553 mNinePatchState = new NinePatchState(mNinePatchState); 554 mMutated = true; 555 } 556 return this; 557 } 558 559 /** 560 * @hide 561 */ clearMutated()562 public void clearMutated() { 563 super.clearMutated(); 564 mMutated = false; 565 } 566 567 @Override onStateChange(int[] stateSet)568 protected boolean onStateChange(int[] stateSet) { 569 final NinePatchState state = mNinePatchState; 570 if (state.mTint != null && state.mBlendMode != null) { 571 mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, 572 state.mBlendMode); 573 return true; 574 } 575 576 return false; 577 } 578 579 @Override isStateful()580 public boolean isStateful() { 581 final NinePatchState s = mNinePatchState; 582 return super.isStateful() || (s.mTint != null && s.mTint.isStateful()); 583 } 584 585 /** @hide */ 586 @Override hasFocusStateSpecified()587 public boolean hasFocusStateSpecified() { 588 return mNinePatchState.mTint != null && mNinePatchState.mTint.hasFocusStateSpecified(); 589 } 590 591 final static class NinePatchState extends ConstantState { 592 @Config int mChangingConfigurations; 593 594 // Values loaded during inflation. 595 @UnsupportedAppUsage 596 NinePatch mNinePatch = null; 597 ColorStateList mTint = null; 598 BlendMode mBlendMode = DEFAULT_BLEND_MODE; 599 Rect mPadding = null; 600 Insets mOpticalInsets = Insets.NONE; 601 float mBaseAlpha = 1.0f; 602 boolean mDither = DEFAULT_DITHER; 603 boolean mAutoMirrored = false; 604 605 int[] mThemeAttrs; 606 NinePatchState()607 NinePatchState() { 608 // Empty constructor. 609 } 610 NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding)611 NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding) { 612 this(ninePatch, padding, null, DEFAULT_DITHER, false); 613 } 614 NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets)615 NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding, 616 @Nullable Rect opticalInsets) { 617 this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false); 618 } 619 NinePatchState(@onNull NinePatch ninePatch, @Nullable Rect padding, @Nullable Rect opticalInsets, boolean dither, boolean autoMirror)620 NinePatchState(@NonNull NinePatch ninePatch, @Nullable Rect padding, 621 @Nullable Rect opticalInsets, boolean dither, boolean autoMirror) { 622 mNinePatch = ninePatch; 623 mPadding = padding; 624 mOpticalInsets = Insets.of(opticalInsets); 625 mDither = dither; 626 mAutoMirrored = autoMirror; 627 } 628 NinePatchState(@onNull NinePatchState orig)629 NinePatchState(@NonNull NinePatchState orig) { 630 mChangingConfigurations = orig.mChangingConfigurations; 631 mNinePatch = orig.mNinePatch; 632 mTint = orig.mTint; 633 mBlendMode = orig.mBlendMode; 634 mPadding = orig.mPadding; 635 mOpticalInsets = orig.mOpticalInsets; 636 mBaseAlpha = orig.mBaseAlpha; 637 mDither = orig.mDither; 638 mAutoMirrored = orig.mAutoMirrored; 639 mThemeAttrs = orig.mThemeAttrs; 640 } 641 642 @Override canApplyTheme()643 public boolean canApplyTheme() { 644 return mThemeAttrs != null 645 || (mTint != null && mTint.canApplyTheme()) 646 || super.canApplyTheme(); 647 } 648 649 @Override newDrawable()650 public Drawable newDrawable() { 651 return new NinePatchDrawable(this, null); 652 } 653 654 @Override newDrawable(Resources res)655 public Drawable newDrawable(Resources res) { 656 return new NinePatchDrawable(this, res); 657 } 658 659 @Override getChangingConfigurations()660 public @Config int getChangingConfigurations() { 661 return mChangingConfigurations 662 | (mTint != null ? mTint.getChangingConfigurations() : 0); 663 } 664 } 665 computeBitmapSize()666 private void computeBitmapSize() { 667 final NinePatch ninePatch = mNinePatchState.mNinePatch; 668 if (ninePatch == null) { 669 return; 670 } 671 672 final int targetDensity = mTargetDensity; 673 final int sourceDensity = ninePatch.getDensity() == Bitmap.DENSITY_NONE ? 674 targetDensity : ninePatch.getDensity(); 675 676 final Insets sourceOpticalInsets = mNinePatchState.mOpticalInsets; 677 if (sourceOpticalInsets != Insets.NONE) { 678 final int left = Drawable.scaleFromDensity( 679 sourceOpticalInsets.left, sourceDensity, targetDensity, true); 680 final int top = Drawable.scaleFromDensity( 681 sourceOpticalInsets.top, sourceDensity, targetDensity, true); 682 final int right = Drawable.scaleFromDensity( 683 sourceOpticalInsets.right, sourceDensity, targetDensity, true); 684 final int bottom = Drawable.scaleFromDensity( 685 sourceOpticalInsets.bottom, sourceDensity, targetDensity, true); 686 mOpticalInsets = Insets.of(left, top, right, bottom); 687 } else { 688 mOpticalInsets = Insets.NONE; 689 } 690 691 final Rect sourcePadding = mNinePatchState.mPadding; 692 if (sourcePadding != null) { 693 if (mPadding == null) { 694 mPadding = new Rect(); 695 } 696 mPadding.left = Drawable.scaleFromDensity( 697 sourcePadding.left, sourceDensity, targetDensity, true); 698 mPadding.top = Drawable.scaleFromDensity( 699 sourcePadding.top, sourceDensity, targetDensity, true); 700 mPadding.right = Drawable.scaleFromDensity( 701 sourcePadding.right, sourceDensity, targetDensity, true); 702 mPadding.bottom = Drawable.scaleFromDensity( 703 sourcePadding.bottom, sourceDensity, targetDensity, true); 704 } else { 705 mPadding = null; 706 } 707 708 mBitmapHeight = Drawable.scaleFromDensity( 709 ninePatch.getHeight(), sourceDensity, targetDensity, true); 710 mBitmapWidth = Drawable.scaleFromDensity( 711 ninePatch.getWidth(), sourceDensity, targetDensity, true); 712 713 final NinePatch.InsetStruct insets = ninePatch.getBitmap().getNinePatchInsets(); 714 if (insets != null) { 715 Rect outlineRect = insets.outlineRect; 716 mOutlineInsets = NinePatch.InsetStruct.scaleInsets(outlineRect.left, outlineRect.top, 717 outlineRect.right, outlineRect.bottom, targetDensity / (float) sourceDensity); 718 mOutlineRadius = Drawable.scaleFromDensity( 719 insets.outlineRadius, sourceDensity, targetDensity); 720 } else { 721 mOutlineInsets = null; 722 } 723 } 724 725 /** 726 * The one constructor to rule them all. This is called by all public 727 * constructors to set the state and initialize local properties. 728 * 729 * @param state constant state to assign to the new drawable 730 */ NinePatchDrawable(@onNull NinePatchState state, @Nullable Resources res)731 private NinePatchDrawable(@NonNull NinePatchState state, @Nullable Resources res) { 732 mNinePatchState = state; 733 734 updateLocalState(res); 735 } 736 737 /** 738 * Initializes local dynamic properties from state. 739 */ updateLocalState(@ullable Resources res)740 private void updateLocalState(@Nullable Resources res) { 741 final NinePatchState state = mNinePatchState; 742 743 // If we can, avoid calling any methods that initialize Paint. 744 if (state.mDither != DEFAULT_DITHER) { 745 setDither(state.mDither); 746 } 747 748 // The nine-patch may have been created without a Resources object, in 749 // which case we should try to match the density of the nine patch (if 750 // available). 751 if (res == null && state.mNinePatch != null) { 752 mTargetDensity = state.mNinePatch.getDensity(); 753 } else { 754 mTargetDensity = Drawable.resolveDensity(res, mTargetDensity); 755 } 756 mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint, state.mBlendMode); 757 computeBitmapSize(); 758 } 759 } 760