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