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