BitmapDrawable.java revision 22594f097242d9de0a538a9b8142f77da9df7ebd
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.BitmapShader;
26import android.graphics.Canvas;
27import android.graphics.ColorFilter;
28import android.graphics.Insets;
29import android.graphics.Matrix;
30import android.graphics.Paint;
31import android.graphics.PixelFormat;
32import android.graphics.PorterDuff;
33import android.graphics.PorterDuff.Mode;
34import android.graphics.drawable.ColorDrawable.ColorState;
35import android.graphics.PorterDuffColorFilter;
36import android.graphics.Rect;
37import android.graphics.Shader;
38import android.graphics.Xfermode;
39import android.util.AttributeSet;
40import android.util.DisplayMetrics;
41import android.util.LayoutDirection;
42import android.view.Gravity;
43
44import com.android.internal.R;
45
46import org.xmlpull.v1.XmlPullParser;
47import org.xmlpull.v1.XmlPullParserException;
48
49import java.io.IOException;
50
51/**
52 * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a
53 * BitmapDrawable from a file path, an input stream, through XML inflation, or from
54 * a {@link android.graphics.Bitmap} object.
55 * <p>It can be defined in an XML file with the <code>&lt;bitmap></code> element.  For more
56 * information, see the guide to <a
57 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
58 * <p>
59 * Also see the {@link android.graphics.Bitmap} class, which handles the management and
60 * transformation of raw bitmap graphics, and should be used when drawing to a
61 * {@link android.graphics.Canvas}.
62 * </p>
63 *
64 * @attr ref android.R.styleable#BitmapDrawable_src
65 * @attr ref android.R.styleable#BitmapDrawable_antialias
66 * @attr ref android.R.styleable#BitmapDrawable_filter
67 * @attr ref android.R.styleable#BitmapDrawable_dither
68 * @attr ref android.R.styleable#BitmapDrawable_gravity
69 * @attr ref android.R.styleable#BitmapDrawable_mipMap
70 * @attr ref android.R.styleable#BitmapDrawable_tileMode
71 */
72public class BitmapDrawable extends Drawable {
73    private static final int DEFAULT_PAINT_FLAGS =
74            Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
75
76    // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}.
77    private static final int TILE_MODE_UNDEFINED = -2;
78    private static final int TILE_MODE_DISABLED = -1;
79    private static final int TILE_MODE_CLAMP = 0;
80    private static final int TILE_MODE_REPEAT = 1;
81    private static final int TILE_MODE_MIRROR = 2;
82
83    private final Rect mDstRect = new Rect();   // Gravity.apply() sets this
84
85    private BitmapState mBitmapState;
86    private PorterDuffColorFilter mTintFilter;
87
88    private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
89
90    private boolean mApplyGravity;
91    private boolean mMutated;
92
93     // These are scaled to match the target density.
94    private int mBitmapWidth;
95    private int mBitmapHeight;
96
97    /** Optical insets due to gravity. */
98    private Insets mOpticalInsets = null;
99
100    // Mirroring matrix for using with Shaders
101    private Matrix mMirrorMatrix;
102
103    /**
104     * Create an empty drawable, not dealing with density.
105     * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
106     * instead to specify a bitmap to draw with and ensure the correct density is set.
107     */
108    @Deprecated
109    public BitmapDrawable() {
110        mBitmapState = new BitmapState((Bitmap) null);
111    }
112
113    /**
114     * Create an empty drawable, setting initial target density based on
115     * the display metrics of the resources.
116     *
117     * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)}
118     * instead to specify a bitmap to draw with.
119     */
120    @SuppressWarnings("unused")
121    @Deprecated
122    public BitmapDrawable(Resources res) {
123        mBitmapState = new BitmapState((Bitmap) null);
124        mBitmapState.mTargetDensity = mTargetDensity;
125    }
126
127    /**
128     * Create drawable from a bitmap, not dealing with density.
129     * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure
130     * that the drawable has correctly set its target density.
131     */
132    @Deprecated
133    public BitmapDrawable(Bitmap bitmap) {
134        this(new BitmapState(bitmap), null, null);
135    }
136
137    /**
138     * Create drawable from a bitmap, setting initial target density based on
139     * the display metrics of the resources.
140     */
141    public BitmapDrawable(Resources res, Bitmap bitmap) {
142        this(new BitmapState(bitmap), res, null);
143        mBitmapState.mTargetDensity = mTargetDensity;
144    }
145
146    /**
147     * Create a drawable by opening a given file path and decoding the bitmap.
148     * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure
149     * that the drawable has correctly set its target density.
150     */
151    @Deprecated
152    public BitmapDrawable(String filepath) {
153        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null);
154        if (mBitmapState.mBitmap == null) {
155            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
156        }
157    }
158
159    /**
160     * Create a drawable by opening a given file path and decoding the bitmap.
161     */
162    @SuppressWarnings("unused")
163    public BitmapDrawable(Resources res, String filepath) {
164        this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null);
165        mBitmapState.mTargetDensity = mTargetDensity;
166        if (mBitmapState.mBitmap == null) {
167            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
168        }
169    }
170
171    /**
172     * Create a drawable by decoding a bitmap from the given input stream.
173     * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure
174     * that the drawable has correctly set its target density.
175     */
176    @Deprecated
177    public BitmapDrawable(java.io.InputStream is) {
178        this(new BitmapState(BitmapFactory.decodeStream(is)), null, null);
179        if (mBitmapState.mBitmap == null) {
180            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
181        }
182    }
183
184    /**
185     * Create a drawable by decoding a bitmap from the given input stream.
186     */
187    @SuppressWarnings("unused")
188    public BitmapDrawable(Resources res, java.io.InputStream is) {
189        this(new BitmapState(BitmapFactory.decodeStream(is)), null, null);
190        mBitmapState.mTargetDensity = mTargetDensity;
191        if (mBitmapState.mBitmap == null) {
192            android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
193        }
194    }
195
196    /**
197     * Returns the paint used to render this drawable.
198     */
199    public final Paint getPaint() {
200        return mBitmapState.mPaint;
201    }
202
203    /**
204     * Returns the bitmap used by this drawable to render. May be null.
205     */
206    public final Bitmap getBitmap() {
207        return mBitmapState.mBitmap;
208    }
209
210    private void computeBitmapSize() {
211        final Bitmap bitmap = mBitmapState.mBitmap;
212        if (bitmap != null) {
213            mBitmapWidth = bitmap.getScaledWidth(mTargetDensity);
214            mBitmapHeight = bitmap.getScaledHeight(mTargetDensity);
215        } else {
216            mBitmapWidth = mBitmapHeight = -1;
217        }
218    }
219
220    private void setBitmap(Bitmap bitmap) {
221        if (mBitmapState.mBitmap != bitmap) {
222            mBitmapState.mBitmap = bitmap;
223            computeBitmapSize();
224            invalidateSelf();
225        }
226    }
227
228    /**
229     * Set the density scale at which this drawable will be rendered. This
230     * method assumes the drawable will be rendered at the same density as the
231     * specified canvas.
232     *
233     * @param canvas The Canvas from which the density scale must be obtained.
234     *
235     * @see android.graphics.Bitmap#setDensity(int)
236     * @see android.graphics.Bitmap#getDensity()
237     */
238    public void setTargetDensity(Canvas canvas) {
239        setTargetDensity(canvas.getDensity());
240    }
241
242    /**
243     * Set the density scale at which this drawable will be rendered.
244     *
245     * @param metrics The DisplayMetrics indicating the density scale for this drawable.
246     *
247     * @see android.graphics.Bitmap#setDensity(int)
248     * @see android.graphics.Bitmap#getDensity()
249     */
250    public void setTargetDensity(DisplayMetrics metrics) {
251        setTargetDensity(metrics.densityDpi);
252    }
253
254    /**
255     * Set the density at which this drawable will be rendered.
256     *
257     * @param density The density scale for this drawable.
258     *
259     * @see android.graphics.Bitmap#setDensity(int)
260     * @see android.graphics.Bitmap#getDensity()
261     */
262    public void setTargetDensity(int density) {
263        if (mTargetDensity != density) {
264            mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
265            if (mBitmapState.mBitmap != null) {
266                computeBitmapSize();
267            }
268            invalidateSelf();
269        }
270    }
271
272    /** Get the gravity used to position/stretch the bitmap within its bounds.
273     * See android.view.Gravity
274     * @return the gravity applied to the bitmap
275     */
276    public int getGravity() {
277        return mBitmapState.mGravity;
278    }
279
280    /** Set the gravity used to position/stretch the bitmap within its bounds.
281        See android.view.Gravity
282     * @param gravity the gravity
283     */
284    public void setGravity(int gravity) {
285        if (mBitmapState.mGravity != gravity) {
286            mBitmapState.mGravity = gravity;
287            mApplyGravity = true;
288            invalidateSelf();
289        }
290    }
291
292    /**
293     * Enables or disables the mipmap hint for this drawable's bitmap.
294     * See {@link Bitmap#setHasMipMap(boolean)} for more information.
295     *
296     * If the bitmap is null calling this method has no effect.
297     *
298     * @param mipMap True if the bitmap should use mipmaps, false otherwise.
299     *
300     * @see #hasMipMap()
301     */
302    public void setMipMap(boolean mipMap) {
303        if (mBitmapState.mBitmap != null) {
304            mBitmapState.mBitmap.setHasMipMap(mipMap);
305            invalidateSelf();
306        }
307    }
308
309    /**
310     * Indicates whether the mipmap hint is enabled on this drawable's bitmap.
311     *
312     * @return True if the mipmap hint is set, false otherwise. If the bitmap
313     *         is null, this method always returns false.
314     *
315     * @see #setMipMap(boolean)
316     * @attr ref android.R.styleable#BitmapDrawable_mipMap
317     */
318    public boolean hasMipMap() {
319        return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap();
320    }
321
322    /**
323     * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects
324     * the edges of the bitmap only so it applies only when the drawable is rotated.
325     *
326     * @param aa True if the bitmap should be anti-aliased, false otherwise.
327     *
328     * @see #hasAntiAlias()
329     */
330    public void setAntiAlias(boolean aa) {
331        mBitmapState.mPaint.setAntiAlias(aa);
332        invalidateSelf();
333    }
334
335    /**
336     * Indicates whether anti-aliasing is enabled for this drawable.
337     *
338     * @return True if anti-aliasing is enabled, false otherwise.
339     *
340     * @see #setAntiAlias(boolean)
341     */
342    public boolean hasAntiAlias() {
343        return mBitmapState.mPaint.isAntiAlias();
344    }
345
346    @Override
347    public void setFilterBitmap(boolean filter) {
348        mBitmapState.mPaint.setFilterBitmap(filter);
349        invalidateSelf();
350    }
351
352    @Override
353    public void setDither(boolean dither) {
354        mBitmapState.mPaint.setDither(dither);
355        invalidateSelf();
356    }
357
358    /**
359     * Indicates the repeat behavior of this drawable on the X axis.
360     *
361     * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
362     *         {@link android.graphics.Shader.TileMode#REPEAT} or
363     *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
364     */
365    public Shader.TileMode getTileModeX() {
366        return mBitmapState.mTileModeX;
367    }
368
369    /**
370     * Indicates the repeat behavior of this drawable on the Y axis.
371     *
372     * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat,
373     *         {@link android.graphics.Shader.TileMode#REPEAT} or
374     *         {@link android.graphics.Shader.TileMode#MIRROR} otherwise.
375     */
376    public Shader.TileMode getTileModeY() {
377        return mBitmapState.mTileModeY;
378    }
379
380    /**
381     * Sets the repeat behavior of this drawable on the X axis. By default, the drawable
382     * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
383     * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
384     * if the bitmap is smaller than this drawable.
385     *
386     * @param mode The repeat mode for this drawable.
387     *
388     * @see #setTileModeY(android.graphics.Shader.TileMode)
389     * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
390     */
391    public void setTileModeX(Shader.TileMode mode) {
392        setTileModeXY(mode, mBitmapState.mTileModeY);
393    }
394
395    /**
396     * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable
397     * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
398     * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
399     * if the bitmap is smaller than this drawable.
400     *
401     * @param mode The repeat mode for this drawable.
402     *
403     * @see #setTileModeX(android.graphics.Shader.TileMode)
404     * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode)
405     */
406    public final void setTileModeY(Shader.TileMode mode) {
407        setTileModeXY(mBitmapState.mTileModeX, mode);
408    }
409
410    /**
411     * Sets the repeat behavior of this drawable on both axis. By default, the drawable
412     * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or
413     * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled)
414     * if the bitmap is smaller than this drawable.
415     *
416     * @param xmode The X repeat mode for this drawable.
417     * @param ymode The Y repeat mode for this drawable.
418     *
419     * @see #setTileModeX(android.graphics.Shader.TileMode)
420     * @see #setTileModeY(android.graphics.Shader.TileMode)
421     */
422    public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
423        final BitmapState state = mBitmapState;
424        if (state.mTileModeX != xmode || state.mTileModeY != ymode) {
425            state.mTileModeX = xmode;
426            state.mTileModeY = ymode;
427            state.mRebuildShader = true;
428            invalidateSelf();
429        }
430    }
431
432    @Override
433    public void setAutoMirrored(boolean mirrored) {
434        if (mBitmapState.mAutoMirrored != mirrored) {
435            mBitmapState.mAutoMirrored = mirrored;
436            invalidateSelf();
437        }
438    }
439
440    @Override
441    public final boolean isAutoMirrored() {
442        return mBitmapState.mAutoMirrored;
443    }
444
445    @Override
446    public int getChangingConfigurations() {
447        return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations;
448    }
449
450    private boolean needMirroring() {
451        return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
452    }
453
454    private void updateMirrorMatrix(float dx) {
455        if (mMirrorMatrix == null) {
456            mMirrorMatrix = new Matrix();
457        }
458        mMirrorMatrix.setTranslate(dx, 0);
459        mMirrorMatrix.preScale(-1.0f, 1.0f);
460    }
461
462    @Override
463    protected void onBoundsChange(Rect bounds) {
464        mApplyGravity = true;
465
466        final Shader shader = mBitmapState.mPaint.getShader();
467        if (shader != null) {
468            if (needMirroring()) {
469                updateMirrorMatrix(bounds.right - bounds.left);
470                shader.setLocalMatrix(mMirrorMatrix);
471            } else {
472                if (mMirrorMatrix != null) {
473                    mMirrorMatrix = null;
474                    shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
475                }
476            }
477        }
478    }
479
480    @Override
481    public void draw(Canvas canvas) {
482        final Bitmap bitmap = mBitmapState.mBitmap;
483        if (bitmap == null) {
484            return;
485        }
486
487        final BitmapState state = mBitmapState;
488        final Paint paint = state.mPaint;
489        if (state.mRebuildShader) {
490            final Shader.TileMode tmx = state.mTileModeX;
491            final Shader.TileMode tmy = state.mTileModeY;
492            if (tmx == null && tmy == null) {
493                paint.setShader(null);
494            } else {
495                paint.setShader(new BitmapShader(bitmap,
496                        tmx == null ? Shader.TileMode.CLAMP : tmx,
497                        tmy == null ? Shader.TileMode.CLAMP : tmy));
498            }
499
500            state.mRebuildShader = false;
501            copyBounds(mDstRect);
502        }
503
504        final int restoreAlpha;
505        if (state.mBaseAlpha != 1.0f) {
506            final Paint p = getPaint();
507            restoreAlpha = p.getAlpha();
508            p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f));
509        } else {
510            restoreAlpha = -1;
511        }
512
513        final boolean clearColorFilter;
514        if (mTintFilter != null && paint.getColorFilter() == null) {
515            paint.setColorFilter(mTintFilter);
516            clearColorFilter = true;
517        } else {
518            clearColorFilter = false;
519        }
520
521        final Shader shader = paint.getShader();
522        final boolean needMirroring = needMirroring();
523        if (shader == null) {
524            if (mApplyGravity) {
525                applyGravity();
526                mApplyGravity = false;
527            }
528
529            if (needMirroring) {
530                canvas.save();
531                // Mirror the bitmap
532                canvas.translate(mDstRect.right - mDstRect.left, 0);
533                canvas.scale(-1.0f, 1.0f);
534            }
535
536            canvas.drawBitmap(bitmap, null, mDstRect, paint);
537
538            if (needMirroring) {
539                canvas.restore();
540            }
541        } else {
542            if (mApplyGravity) {
543                copyBounds(mDstRect);
544                mApplyGravity = false;
545            }
546
547            if (needMirroring) {
548                // Mirror the bitmap
549                updateMirrorMatrix(mDstRect.right - mDstRect.left);
550                shader.setLocalMatrix(mMirrorMatrix);
551            } else {
552                if (mMirrorMatrix != null) {
553                    mMirrorMatrix = null;
554                    shader.setLocalMatrix(Matrix.IDENTITY_MATRIX);
555                }
556            }
557
558            canvas.drawRect(mDstRect, paint);
559        }
560
561        if (clearColorFilter) {
562            paint.setColorFilter(null);
563        }
564
565        if (restoreAlpha >= 0) {
566            paint.setAlpha(restoreAlpha);
567        }
568    }
569
570    /**
571     * @hide
572     */
573    @Override
574    public Insets getOpticalInsets() {
575        if (mApplyGravity && mBitmapState.mPaint.getShader() == null) {
576            applyGravity();
577            mApplyGravity = false;
578        }
579        return mOpticalInsets == null ? Insets.NONE : mOpticalInsets;
580    }
581
582    private void applyGravity() {
583        final Rect bounds = getBounds();
584        final int layoutDirection = getLayoutDirection();
585        Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight,
586                bounds, mDstRect, layoutDirection);
587
588        final int left = mDstRect.left - bounds.left;
589        final int top = mDstRect.top - bounds.top;
590        final int right = bounds.right - mDstRect.right;
591        final int bottom = bounds.bottom - mDstRect.bottom;
592        mOpticalInsets = Insets.of(left, top, right, bottom);
593    }
594
595    @Override
596    public void setAlpha(int alpha) {
597        final int oldAlpha = mBitmapState.mPaint.getAlpha();
598        if (alpha != oldAlpha) {
599            mBitmapState.mPaint.setAlpha(alpha);
600            invalidateSelf();
601        }
602    }
603
604    @Override
605    public int getAlpha() {
606        return mBitmapState.mPaint.getAlpha();
607    }
608
609    @Override
610    public void setColorFilter(ColorFilter cf) {
611        mBitmapState.mPaint.setColorFilter(cf);
612        invalidateSelf();
613    }
614
615    @Override
616    public ColorFilter getColorFilter() {
617        return mBitmapState.mPaint.getColorFilter();
618    }
619
620    @Override
621    public void setTint(ColorStateList tint, PorterDuff.Mode tintMode) {
622        final BitmapState state = mBitmapState;
623        state.mTint = tint;
624        state.mTintMode = tintMode;
625
626        mTintFilter = updateTintFilter(mTintFilter, tint, tintMode);
627        invalidateSelf();
628    }
629
630    /**
631     * @hide only needed by a hack within ProgressBar
632     */
633    public ColorStateList getTint() {
634        return mBitmapState.mTint;
635    }
636
637    /**
638     * @hide only needed by a hack within ProgressBar
639     */
640    public Mode getTintMode() {
641        return mBitmapState.mTintMode;
642    }
643
644    /**
645     * @hide Candidate for future API inclusion
646     */
647    @Override
648    public void setXfermode(Xfermode xfermode) {
649        mBitmapState.mPaint.setXfermode(xfermode);
650        invalidateSelf();
651    }
652
653    /**
654     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
655     * that comes from the same resource.
656     *
657     * @return This drawable.
658     */
659    @Override
660    public Drawable mutate() {
661        if (!mMutated && super.mutate() == this) {
662            mBitmapState = new BitmapState(mBitmapState);
663            mMutated = true;
664        }
665        return this;
666    }
667
668    @Override
669    protected boolean onStateChange(int[] stateSet) {
670        final BitmapState state = mBitmapState;
671        if (state.mTint != null && state.mTintMode != null) {
672            mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
673            return true;
674        }
675        return false;
676    }
677
678    @Override
679    public boolean isStateful() {
680        final BitmapState s = mBitmapState;
681        return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
682    }
683
684    @Override
685    public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
686            throws XmlPullParserException, IOException {
687        super.inflate(r, parser, attrs, theme);
688
689        final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable);
690        updateStateFromTypedArray(a);
691        verifyState(a);
692        a.recycle();
693    }
694
695    /**
696     * Ensures all required attributes are set.
697     *
698     * @throws XmlPullParserException if any required attributes are missing
699     */
700    private void verifyState(TypedArray a) throws XmlPullParserException {
701        final BitmapState state = mBitmapState;
702        if (state.mBitmap == null) {
703            throw new XmlPullParserException(a.getPositionDescription() +
704                    ": <bitmap> requires a valid src attribute");
705        }
706    }
707
708    /**
709     * Updates the constant state from the values in the typed array.
710     */
711    private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
712        final Resources r = a.getResources();
713        final BitmapState state = mBitmapState;
714
715        // Extract the theme attributes, if any.
716        final int[] themeAttrs = a.extractThemeAttrs();
717        state.mThemeAttrs = themeAttrs;
718
719        final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0);
720        if (srcResId != 0) {
721            final Bitmap bitmap = BitmapFactory.decodeResource(r, srcResId);
722            if (bitmap == null) {
723                throw new XmlPullParserException(a.getPositionDescription() +
724                        ": <bitmap> requires a valid src attribute");
725            }
726
727            state.mBitmap = bitmap;
728        }
729
730        state.mTargetDensity = r.getDisplayMetrics().densityDpi;
731
732        final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false;
733        setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap));
734
735        state.mAutoMirrored = a.getBoolean(
736                R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored);
737        state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha);
738
739        final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1);
740        if (tintMode != -1) {
741            state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN);
742        }
743
744        final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint);
745        if (tint != null) {
746            state.mTint = tint;
747        }
748
749        final Paint paint = mBitmapState.mPaint;
750        paint.setAntiAlias(a.getBoolean(
751                R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()));
752        paint.setFilterBitmap(a.getBoolean(
753                R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()));
754        paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither()));
755
756        setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity));
757
758        final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED);
759        if (tileMode != TILE_MODE_UNDEFINED) {
760            final Shader.TileMode mode = parseTileMode(tileMode);
761            setTileModeXY(mode, mode);
762        }
763
764        final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED);
765        if (tileModeX != TILE_MODE_UNDEFINED) {
766            setTileModeX(parseTileMode(tileModeX));
767        }
768
769        final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED);
770        if (tileModeY != TILE_MODE_UNDEFINED) {
771            setTileModeY(parseTileMode(tileModeY));
772        }
773
774        // Update local properties.
775        initializeWithState(state, r);
776    }
777
778    @Override
779    public void applyTheme(Theme t) {
780        super.applyTheme(t);
781
782        final BitmapState state = mBitmapState;
783        if (state == null || state.mThemeAttrs == null) {
784            return;
785        }
786
787        final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable);
788        try {
789            updateStateFromTypedArray(a);
790        } catch (XmlPullParserException e) {
791            throw new RuntimeException(e);
792        } finally {
793            a.recycle();
794        }
795    }
796
797    private static Shader.TileMode parseTileMode(int tileMode) {
798        switch (tileMode) {
799            case TILE_MODE_CLAMP:
800                return Shader.TileMode.CLAMP;
801            case TILE_MODE_REPEAT:
802                return Shader.TileMode.REPEAT;
803            case TILE_MODE_MIRROR:
804                return Shader.TileMode.MIRROR;
805            default:
806                return null;
807        }
808    }
809
810    @Override
811    public boolean canApplyTheme() {
812        return mBitmapState != null && mBitmapState.mThemeAttrs != null;
813    }
814
815    @Override
816    public int getIntrinsicWidth() {
817        return mBitmapWidth;
818    }
819
820    @Override
821    public int getIntrinsicHeight() {
822        return mBitmapHeight;
823    }
824
825    @Override
826    public int getOpacity() {
827        if (mBitmapState.mGravity != Gravity.FILL) {
828            return PixelFormat.TRANSLUCENT;
829        }
830
831        final Bitmap bitmap = mBitmapState.mBitmap;
832        return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ?
833                PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
834    }
835
836    @Override
837    public final ConstantState getConstantState() {
838        mBitmapState.mChangingConfigurations = getChangingConfigurations();
839        return mBitmapState;
840    }
841
842    final static class BitmapState extends ConstantState {
843        final Paint mPaint;
844
845        // Values loaded during inflation.
846        int[] mThemeAttrs = null;
847        Bitmap mBitmap = null;
848        ColorStateList mTint = null;
849        Mode mTintMode = Mode.SRC_IN;
850        int mGravity = Gravity.FILL;
851        float mBaseAlpha = 1.0f;
852        Shader.TileMode mTileModeX = null;
853        Shader.TileMode mTileModeY = null;
854        int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
855        boolean mAutoMirrored = false;
856
857        int mChangingConfigurations;
858        boolean mRebuildShader;
859
860        BitmapState(Bitmap bitmap) {
861            mBitmap = bitmap;
862            mPaint = new Paint(DEFAULT_PAINT_FLAGS);
863        }
864
865        BitmapState(BitmapState bitmapState) {
866            mBitmap = bitmapState.mBitmap;
867            mTint = bitmapState.mTint;
868            mTintMode = bitmapState.mTintMode;
869            mThemeAttrs = bitmapState.mThemeAttrs;
870            mChangingConfigurations = bitmapState.mChangingConfigurations;
871            mGravity = bitmapState.mGravity;
872            mTileModeX = bitmapState.mTileModeX;
873            mTileModeY = bitmapState.mTileModeY;
874            mTargetDensity = bitmapState.mTargetDensity;
875            mBaseAlpha = bitmapState.mBaseAlpha;
876            mPaint = new Paint(bitmapState.mPaint);
877            mRebuildShader = bitmapState.mRebuildShader;
878            mAutoMirrored = bitmapState.mAutoMirrored;
879        }
880
881        @Override
882        public boolean canApplyTheme() {
883            return mThemeAttrs != null;
884        }
885
886        @Override
887        public Bitmap getBitmap() {
888            return mBitmap;
889        }
890
891        @Override
892        public Drawable newDrawable() {
893            return new BitmapDrawable(this, null, null);
894        }
895
896        @Override
897        public Drawable newDrawable(Resources res) {
898            return new BitmapDrawable(this, res, null);
899        }
900
901        @Override
902        public Drawable newDrawable(Resources res, Theme theme) {
903            return new BitmapDrawable(this, res, theme);
904        }
905
906        @Override
907        public int getChangingConfigurations() {
908            return mChangingConfigurations;
909        }
910    }
911
912    /**
913     * The one constructor to rule them all. This is called by all public
914     * constructors to set the state and initialize local properties.
915     */
916    private BitmapDrawable(BitmapState state, Resources res, Theme theme) {
917        if (theme != null && state.canApplyTheme()) {
918            // If we need to apply a theme, implicitly mutate.
919            mBitmapState = new BitmapState(state);
920            applyTheme(theme);
921        } else {
922            mBitmapState = state;
923        }
924
925        initializeWithState(state, res);
926    }
927
928    /**
929     * Initializes local dynamic properties from state. This should be called
930     * after significant state changes, e.g. from the One True Constructor and
931     * after inflating or applying a theme.
932     */
933    private void initializeWithState(BitmapState state, Resources res) {
934        if (res != null) {
935            mTargetDensity = res.getDisplayMetrics().densityDpi;
936        } else {
937            mTargetDensity = state.mTargetDensity;
938        }
939
940        mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
941        computeBitmapSize();
942    }
943}
944