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