• 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 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.mChangingConfigurations;
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 cf)341     public void setColorFilter(ColorFilter cf) {
342         if (mPaint == null && cf == null) {
343             // Fast common case -- leave at no color filter.
344             return;
345         }
346         getPaint().setColorFilter(cf);
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
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 
406     /**
407      * Updates the constant state from the values in the typed array.
408      */
updateStateFromTypedArray(TypedArray a)409     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
410         final Resources r = a.getResources();
411         final NinePatchState state = mNinePatchState;
412 
413         // Account for any configuration changes.
414         state.mChangingConfigurations |= a.getChangingConfigurations();
415 
416         // Extract the theme attributes, if any.
417         state.mThemeAttrs = a.extractThemeAttrs();
418 
419         state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, state.mDither);
420 
421         final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
422         if (srcResId != 0) {
423             final BitmapFactory.Options options = new BitmapFactory.Options();
424             options.inDither = !state.mDither;
425             options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;
426 
427             final Rect padding = new Rect();
428             final Rect opticalInsets = new Rect();
429             Bitmap bitmap = null;
430 
431             try {
432                 final TypedValue value = new TypedValue();
433                 final InputStream is = r.openRawResource(srcResId, value);
434 
435                 bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
436 
437                 is.close();
438             } catch (IOException e) {
439                 // Ignore
440             }
441 
442             if (bitmap == null) {
443                 throw new XmlPullParserException(a.getPositionDescription() +
444                         ": <nine-patch> requires a valid src attribute");
445             } else if (bitmap.getNinePatchChunk() == null) {
446                 throw new XmlPullParserException(a.getPositionDescription() +
447                         ": <nine-patch> requires a valid 9-patch source image");
448             }
449 
450             bitmap.getOpticalInsets(opticalInsets);
451 
452             state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
453             state.mPadding = padding;
454             state.mOpticalInsets = Insets.of(opticalInsets);
455         }
456 
457         state.mAutoMirrored = a.getBoolean(
458                 R.styleable.NinePatchDrawable_autoMirrored, state.mAutoMirrored);
459         state.mBaseAlpha = a.getFloat(R.styleable.NinePatchDrawable_alpha, state.mBaseAlpha);
460 
461         final int tintMode = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1);
462         if (tintMode != -1) {
463             state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
464         }
465 
466         final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint);
467         if (tint != null) {
468             state.mTint = tint;
469         }
470 
471         // Update local properties.
472         initializeWithState(state, r);
473 
474         // Push density applied by setNinePatchState into state.
475         state.mTargetDensity = mTargetDensity;
476     }
477 
478     @Override
applyTheme(Theme t)479     public void applyTheme(Theme t) {
480         super.applyTheme(t);
481 
482         final NinePatchState state = mNinePatchState;
483         if (state == null || state.mThemeAttrs == null) {
484             return;
485         }
486 
487         final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.NinePatchDrawable);
488         try {
489             updateStateFromTypedArray(a);
490         } catch (XmlPullParserException e) {
491             throw new RuntimeException(e);
492         } finally {
493             a.recycle();
494         }
495     }
496 
497     @Override
canApplyTheme()498     public boolean canApplyTheme() {
499         return mNinePatchState != null && mNinePatchState.mThemeAttrs != null;
500     }
501 
getPaint()502     public Paint getPaint() {
503         if (mPaint == null) {
504             mPaint = new Paint();
505             mPaint.setDither(DEFAULT_DITHER);
506         }
507         return mPaint;
508     }
509 
510     /**
511      * Retrieves the width of the source .png file (before resizing).
512      */
513     @Override
getIntrinsicWidth()514     public int getIntrinsicWidth() {
515         return mBitmapWidth;
516     }
517 
518     /**
519      * Retrieves the height of the source .png file (before resizing).
520      */
521     @Override
getIntrinsicHeight()522     public int getIntrinsicHeight() {
523         return mBitmapHeight;
524     }
525 
526     @Override
getMinimumWidth()527     public int getMinimumWidth() {
528         return mBitmapWidth;
529     }
530 
531     @Override
getMinimumHeight()532     public int getMinimumHeight() {
533         return mBitmapHeight;
534     }
535 
536     /**
537      * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat}
538      * value of OPAQUE or TRANSLUCENT.
539      */
540     @Override
getOpacity()541     public int getOpacity() {
542         return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ?
543                 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
544     }
545 
546     @Override
getTransparentRegion()547     public Region getTransparentRegion() {
548         return mNinePatch.getTransparentRegion(getBounds());
549     }
550 
551     @Override
getConstantState()552     public ConstantState getConstantState() {
553         mNinePatchState.mChangingConfigurations = getChangingConfigurations();
554         return mNinePatchState;
555     }
556 
557     @Override
mutate()558     public Drawable mutate() {
559         if (!mMutated && super.mutate() == this) {
560             mNinePatchState = new NinePatchState(mNinePatchState);
561             mNinePatch = mNinePatchState.mNinePatch;
562             mMutated = true;
563         }
564         return this;
565     }
566 
567     /**
568      * @hide
569      */
clearMutated()570     public void clearMutated() {
571         super.clearMutated();
572         mMutated = false;
573     }
574 
575     @Override
onStateChange(int[] stateSet)576     protected boolean onStateChange(int[] stateSet) {
577         final NinePatchState state = mNinePatchState;
578         if (state.mTint != null && state.mTintMode != null) {
579             mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
580             return true;
581         }
582 
583         return false;
584     }
585 
586     @Override
isStateful()587     public boolean isStateful() {
588         final NinePatchState s = mNinePatchState;
589         return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
590     }
591 
592     final static class NinePatchState extends ConstantState {
593         // Values loaded during inflation.
594         int[] mThemeAttrs = null;
595         NinePatch mNinePatch = null;
596         ColorStateList mTint = null;
597         Mode mTintMode = DEFAULT_TINT_MODE;
598         Rect mPadding = null;
599         Insets mOpticalInsets = Insets.NONE;
600         float mBaseAlpha = 1.0f;
601         boolean mDither = DEFAULT_DITHER;
602         int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
603         boolean mAutoMirrored = false;
604 
605         int mChangingConfigurations;
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 
629         // Copy constructor
630 
NinePatchState(@onNull NinePatchState state)631         NinePatchState(@NonNull NinePatchState state) {
632             // We don't deep-copy any fields because they are all immutable.
633             mNinePatch = state.mNinePatch;
634             mTint = state.mTint;
635             mTintMode = state.mTintMode;
636             mThemeAttrs = state.mThemeAttrs;
637             mPadding = state.mPadding;
638             mOpticalInsets = state.mOpticalInsets;
639             mBaseAlpha = state.mBaseAlpha;
640             mDither = state.mDither;
641             mChangingConfigurations = state.mChangingConfigurations;
642             mTargetDensity = state.mTargetDensity;
643             mAutoMirrored = state.mAutoMirrored;
644         }
645 
646         @Override
canApplyTheme()647         public boolean canApplyTheme() {
648             return mThemeAttrs != null;
649         }
650 
651         @Override
addAtlasableBitmaps(Collection<Bitmap> atlasList)652         public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
653             final Bitmap bitmap = mNinePatch.getBitmap();
654             if (isAtlasable(bitmap) && atlasList.add(bitmap)) {
655                 return bitmap.getWidth() * bitmap.getHeight();
656             }
657             return 0;
658         }
659 
660         @Override
newDrawable()661         public Drawable newDrawable() {
662             return new NinePatchDrawable(this, null);
663         }
664 
665         @Override
newDrawable(Resources res)666         public Drawable newDrawable(Resources res) {
667             return new NinePatchDrawable(this, res);
668         }
669 
670         @Override
getChangingConfigurations()671         public int getChangingConfigurations() {
672             return mChangingConfigurations;
673         }
674     }
675 
676     /**
677      * The one constructor to rule them all. This is called by all public
678      * constructors to set the state and initialize local properties.
679      */
NinePatchDrawable(NinePatchState state, Resources res)680     private NinePatchDrawable(NinePatchState state, Resources res) {
681         mNinePatchState = state;
682 
683         initializeWithState(mNinePatchState, res);
684     }
685 
686     /**
687      * Initializes local dynamic properties from state.
688      */
initializeWithState(NinePatchState state, Resources res)689     private void initializeWithState(NinePatchState state, Resources res) {
690         if (res != null) {
691             mTargetDensity = res.getDisplayMetrics().densityDpi;
692         } else {
693             mTargetDensity = state.mTargetDensity;
694         }
695 
696         // If we can, avoid calling any methods that initialize Paint.
697         if (state.mDither != DEFAULT_DITHER) {
698             setDither(state.mDither);
699         }
700 
701         // Make a local copy of the padding.
702         if (state.mPadding != null) {
703             mPadding = new Rect(state.mPadding);
704         }
705 
706         mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
707         setNinePatch(state.mNinePatch);
708     }
709 }
710