NinePatchDrawable.java revision 11ea33471e1a14a8594f0b2cd012d86340dd3bd8
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.graphics.*;
20import android.content.res.Resources;
21import android.content.res.TypedArray;
22import android.util.AttributeSet;
23import android.util.DisplayMetrics;
24import android.util.TypedValue;
25import org.xmlpull.v1.XmlPullParser;
26import org.xmlpull.v1.XmlPullParserException;
27
28import java.io.IOException;
29import java.io.InputStream;
30
31/**
32 *
33 * A resizeable bitmap, with stretchable areas that you define. This type of image
34 * is defined in a .png file with a special format, described in <a link="../../../resources.html#ninepatch">
35 * Resources</a>.
36 *
37 */
38public class NinePatchDrawable extends Drawable {
39    private NinePatchState mNinePatchState;
40    private NinePatch mNinePatch;
41    private Rect mPadding;
42    private Paint mPaint;
43    private boolean mMutated;
44
45    private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
46
47    // These are scaled to match the target density.
48    private int mBitmapWidth;
49    private int mBitmapHeight;
50
51    NinePatchDrawable() {
52    }
53
54    /**
55     * Create drawable from raw nine-patch data, not dealing with density.
56     * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
57     * to ensure that the drawable has correctly set its target density.
58     */
59    public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
60        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding));
61    }
62
63    /**
64     * Create drawable from raw nine-patch data, setting initial target density
65     * based on the display metrics of the resources.
66     */
67    public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
68            Rect padding, String srcName) {
69        this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding));
70        if (res != null) {
71            setTargetDensity(res.getDisplayMetrics());
72            mNinePatchState.mTargetDensity = mTargetDensity;
73        }
74    }
75
76    /**
77     * Create drawable from existing nine-patch, not dealing with density.
78     * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
79     * to ensure that the drawable has correctly set its target density.
80     */
81    public NinePatchDrawable(NinePatch patch) {
82        this(new NinePatchState(patch, null));
83    }
84
85    /**
86     * Create drawable from existing nine-patch, setting initial target density
87     * based on the display metrics of the resources.
88     */
89    public NinePatchDrawable(Resources res, NinePatch patch) {
90        this(new NinePatchState(patch, null));
91        if (res != null) {
92            setTargetDensity(res.getDisplayMetrics());
93            mNinePatchState.mTargetDensity = mTargetDensity;
94        }
95    }
96
97    private void setNinePatchState(NinePatchState state) {
98        mNinePatchState = state;
99        mNinePatch = state.mNinePatch;
100        mPadding = state.mPadding;
101        mTargetDensity = state.mTargetDensity;
102        if (state.mDither) setDither(state.mDither);
103        if (mNinePatch != null) {
104            computeBitmapSize();
105        }
106    }
107
108    /**
109     * Set the density scale at which this drawable will be rendered. This
110     * method assumes the drawable will be rendered at the same density as the
111     * specified canvas.
112     *
113     * @param canvas The Canvas from which the density scale must be obtained.
114     *
115     * @see android.graphics.Bitmap#setDensity(int)
116     * @see android.graphics.Bitmap#getDensity()
117     */
118    public void setTargetDensity(Canvas canvas) {
119        setTargetDensity(canvas.getDensity());
120    }
121
122    /**
123     * Set the density scale at which this drawable will be rendered.
124     *
125     * @param metrics The DisplayMetrics indicating the density scale for this drawable.
126     *
127     * @see android.graphics.Bitmap#setDensity(int)
128     * @see android.graphics.Bitmap#getDensity()
129     */
130    public void setTargetDensity(DisplayMetrics metrics) {
131        mTargetDensity = metrics.densityDpi;
132        if (mNinePatch != null) {
133            computeBitmapSize();
134        }
135    }
136
137    /**
138     * Set the density at which this drawable will be rendered.
139     *
140     * @param density The density scale for this drawable.
141     *
142     * @see android.graphics.Bitmap#setDensity(int)
143     * @see android.graphics.Bitmap#getDensity()
144     */
145    public void setTargetDensity(int density) {
146        mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
147        if (mNinePatch != null) {
148            computeBitmapSize();
149        }
150    }
151
152    private void computeBitmapSize() {
153        final int sdensity = mNinePatch.getDensity();
154        final int tdensity = mTargetDensity;
155        mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(),
156                sdensity, tdensity);
157        mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(),
158                sdensity, tdensity);
159    }
160
161    // overrides
162
163    @Override
164    public void draw(Canvas canvas) {
165        mNinePatch.draw(canvas, getBounds(), mPaint);
166    }
167
168    @Override
169    public int getChangingConfigurations() {
170        return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations;
171    }
172
173    @Override
174    public boolean getPadding(Rect padding) {
175        padding.set(mPadding);
176        return true;
177    }
178
179    @Override
180    public void setAlpha(int alpha) {
181        getPaint().setAlpha(alpha);
182    }
183
184    @Override
185    public void setColorFilter(ColorFilter cf) {
186        getPaint().setColorFilter(cf);
187    }
188
189    @Override
190    public void setDither(boolean dither) {
191        getPaint().setDither(dither);
192    }
193
194    @Override
195    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
196            throws XmlPullParserException, IOException {
197        super.inflate(r, parser, attrs);
198
199        TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.NinePatchDrawable);
200
201        final int id = a.getResourceId(com.android.internal.R.styleable.NinePatchDrawable_src, 0);
202        if (id == 0) {
203            throw new XmlPullParserException(parser.getPositionDescription() +
204                    ": <nine-patch> requires a valid src attribute");
205        }
206
207        final boolean dither = a.getBoolean(
208                com.android.internal.R.styleable.NinePatchDrawable_dither, false);
209        final BitmapFactory.Options options = new BitmapFactory.Options();
210        if (dither) {
211            options.inDither = false;
212        }
213        options.inScreenDensity = DisplayMetrics.DENSITY_DEVICE;
214
215        final Rect padding = new Rect();
216        Bitmap bitmap = null;
217
218        try {
219            final TypedValue value = new TypedValue();
220            final InputStream is = r.openRawResource(id, value);
221
222            bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
223
224            is.close();
225        } catch (IOException e) {
226            // Ignore
227        }
228
229        if (bitmap == null) {
230            throw new XmlPullParserException(parser.getPositionDescription() +
231                    ": <nine-patch> requires a valid src attribute");
232        } else if (bitmap.getNinePatchChunk() == null) {
233            throw new XmlPullParserException(parser.getPositionDescription() +
234                    ": <nine-patch> requires a valid 9-patch source image");
235        }
236
237        setNinePatchState(new NinePatchState(
238                new NinePatch(bitmap, bitmap.getNinePatchChunk(), "XML 9-patch"), padding, dither));
239        mNinePatchState.mTargetDensity = mTargetDensity;
240
241        a.recycle();
242    }
243
244
245    public Paint getPaint() {
246        if (mPaint == null) {
247            mPaint = new Paint();
248        }
249        return mPaint;
250    }
251
252    /**
253     * Retrieves the width of the source .png file (before resizing).
254     */
255    @Override
256    public int getIntrinsicWidth() {
257        return mBitmapWidth;
258    }
259
260    /**
261     * Retrieves the height of the source .png file (before resizing).
262     */
263    @Override
264    public int getIntrinsicHeight() {
265        return mBitmapHeight;
266    }
267
268    @Override
269    public int getMinimumWidth() {
270        return mBitmapWidth;
271    }
272
273    @Override
274    public int getMinimumHeight() {
275        return mBitmapHeight;
276    }
277
278    /**
279     * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat}
280     * value of OPAQUE or TRANSLUCENT.
281     */
282    @Override
283    public int getOpacity() {
284        return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ?
285                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
286    }
287
288    @Override
289    public Region getTransparentRegion() {
290        return mNinePatch.getTransparentRegion(getBounds());
291    }
292
293    @Override
294    public ConstantState getConstantState() {
295        mNinePatchState.mChangingConfigurations = super.getChangingConfigurations();
296        return mNinePatchState;
297    }
298
299    @Override
300    public Drawable mutate() {
301        if (!mMutated && super.mutate() == this) {
302            mNinePatchState = new NinePatchState(mNinePatchState);
303            mNinePatch = mNinePatchState.mNinePatch;
304            mPadding = mNinePatchState.mPadding;
305            mMutated = true;
306        }
307        return this;
308    }
309
310    final static class NinePatchState extends ConstantState {
311        final NinePatch mNinePatch;
312        final Rect mPadding;
313        final boolean mDither;
314        int mChangingConfigurations;
315        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
316
317        NinePatchState(NinePatch ninePatch, Rect padding) {
318            this(ninePatch, padding, false);
319        }
320
321        NinePatchState(NinePatch ninePatch, Rect rect, boolean dither) {
322            mNinePatch = ninePatch;
323            mPadding = rect;
324            mDither = dither;
325        }
326
327        NinePatchState(NinePatchState state) {
328            mNinePatch = new NinePatch(state.mNinePatch);
329            mPadding = new Rect(state.mPadding);
330            mDither = state.mDither;
331            mChangingConfigurations = state.mChangingConfigurations;
332            mTargetDensity = state.mTargetDensity;
333        }
334
335        @Override
336        public Drawable newDrawable() {
337            return new NinePatchDrawable(this);
338        }
339
340        @Override
341        public int getChangingConfigurations() {
342            return mChangingConfigurations;
343        }
344    }
345
346    private NinePatchDrawable(NinePatchState state) {
347        setNinePatchState(state);
348    }
349}
350
351