/*
 * Copyright (C) 2006 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.graphics.drawable;

import android.annotation.NonNull;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.ImageDecoder;
import android.graphics.Insets;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.LayoutDirection;
import android.util.TypedValue;
import android.view.Gravity;

import com.android.internal.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a BitmapDrawable from a file path, an input stream, through XML inflation, or from a Bitmap object.

It can be defined in an XML file with the <bitmap> element. For more information, see the guide to Drawable Resources.

Also see the Bitmap class, which handles the management and transformation of raw bitmap graphics, and should be used when drawing to a Canvas.

@attrref android.R.styleable#BitmapDrawable_src
@attrref android.R.styleable#BitmapDrawable_antialias
@attrref android.R.styleable#BitmapDrawable_filter
@attrref android.R.styleable#BitmapDrawable_dither
@attrref android.R.styleable#BitmapDrawable_gravity
@attrref android.R.styleable#BitmapDrawable_mipMap
@attrref android.R.styleable#BitmapDrawable_tileMode
/** * A Drawable that wraps a bitmap and can be tiled, stretched, or aligned. You can create a * BitmapDrawable from a file path, an input stream, through XML inflation, or from * a {@link android.graphics.Bitmap} object. * <p>It can be defined in an XML file with the <code>&lt;bitmap></code> element. For more * information, see the guide to <a * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> * <p> * Also see the {@link android.graphics.Bitmap} class, which handles the management and * transformation of raw bitmap graphics, and should be used when drawing to a * {@link android.graphics.Canvas}. * </p> * * @attr ref android.R.styleable#BitmapDrawable_src * @attr ref android.R.styleable#BitmapDrawable_antialias * @attr ref android.R.styleable#BitmapDrawable_filter * @attr ref android.R.styleable#BitmapDrawable_dither * @attr ref android.R.styleable#BitmapDrawable_gravity * @attr ref android.R.styleable#BitmapDrawable_mipMap * @attr ref android.R.styleable#BitmapDrawable_tileMode */
public class BitmapDrawable extends Drawable { private static final int DEFAULT_PAINT_FLAGS = Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG; // Constants for {@link android.R.styleable#BitmapDrawable_tileMode}. private static final int TILE_MODE_UNDEFINED = -2; private static final int TILE_MODE_DISABLED = -1; private static final int TILE_MODE_CLAMP = 0; private static final int TILE_MODE_REPEAT = 1; private static final int TILE_MODE_MIRROR = 2; private final Rect mDstRect = new Rect(); // #updateDstRectAndInsetsIfDirty() sets this private BitmapState mBitmapState; private PorterDuffColorFilter mTintFilter; private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; private boolean mDstRectAndInsetsDirty = true; private boolean mMutated; // These are scaled to match the target density. private int mBitmapWidth; private int mBitmapHeight;
Optical insets due to gravity.
/** Optical insets due to gravity. */
private Insets mOpticalInsets = Insets.NONE; // Mirroring matrix for using with Shaders private Matrix mMirrorMatrix;
Create an empty drawable, not dealing with density.
Deprecated:Use BitmapDrawable(Resources, Bitmap) instead to specify a bitmap to draw with and ensure the correct density is set.
/** * Create an empty drawable, not dealing with density. * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} * instead to specify a bitmap to draw with and ensure the correct density is set. */
@Deprecated public BitmapDrawable() { init(new BitmapState((Bitmap) null), null); }
Create an empty drawable, setting initial target density based on the display metrics of the resources.
Deprecated:Use BitmapDrawable(Resources, Bitmap) instead to specify a bitmap to draw with.
/** * Create an empty drawable, setting initial target density based on * the display metrics of the resources. * * @deprecated Use {@link #BitmapDrawable(android.content.res.Resources, android.graphics.Bitmap)} * instead to specify a bitmap to draw with. */
@SuppressWarnings("unused") @Deprecated public BitmapDrawable(Resources res) { init(new BitmapState((Bitmap) null), res); }
Create drawable from a bitmap, not dealing with density.
Deprecated:Use BitmapDrawable(Resources, Bitmap) to ensure that the drawable has correctly set its target density.
/** * Create drawable from a bitmap, not dealing with density. * @deprecated Use {@link #BitmapDrawable(Resources, Bitmap)} to ensure * that the drawable has correctly set its target density. */
@Deprecated public BitmapDrawable(Bitmap bitmap) { init(new BitmapState(bitmap), null); }
Create drawable from a bitmap, setting initial target density based on the display metrics of the resources.
/** * Create drawable from a bitmap, setting initial target density based on * the display metrics of the resources. */
public BitmapDrawable(Resources res, Bitmap bitmap) { init(new BitmapState(bitmap), res); }
Create a drawable by opening a given file path and decoding the bitmap.
Deprecated:Use BitmapDrawable(Resources, String) to ensure that the drawable has correctly set its target density.
/** * Create a drawable by opening a given file path and decoding the bitmap. * @deprecated Use {@link #BitmapDrawable(Resources, String)} to ensure * that the drawable has correctly set its target density. */
@Deprecated public BitmapDrawable(String filepath) { this(null, filepath); }
Create a drawable by opening a given file path and decoding the bitmap.
/** * Create a drawable by opening a given file path and decoding the bitmap. */
@SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" }) public BitmapDrawable(Resources res, String filepath) { Bitmap bitmap = null; try (FileInputStream stream = new FileInputStream(filepath)) { bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, stream), (decoder, info, src) -> { decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); }); } catch (Exception e) { /* do nothing. This matches the behavior of BitmapFactory.decodeFile() If the exception happened on decode, mBitmapState.mBitmap will be null. */ } finally { init(new BitmapState(bitmap), res); if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); } } }
Create a drawable by decoding a bitmap from the given input stream.
Deprecated:Use BitmapDrawable(Resources, InputStream) to ensure that the drawable has correctly set its target density.
/** * Create a drawable by decoding a bitmap from the given input stream. * @deprecated Use {@link #BitmapDrawable(Resources, java.io.InputStream)} to ensure * that the drawable has correctly set its target density. */
@Deprecated public BitmapDrawable(java.io.InputStream is) { this(null, is); }
Create a drawable by decoding a bitmap from the given input stream.
/** * Create a drawable by decoding a bitmap from the given input stream. */
@SuppressWarnings({ "unused", "ChainingConstructorIgnoresParameter" }) public BitmapDrawable(Resources res, java.io.InputStream is) { Bitmap bitmap = null; try { bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(res, is), (decoder, info, src) -> { decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); }); } catch (Exception e) { /* do nothing. This matches the behavior of BitmapFactory.decodeStream() If the exception happened on decode, mBitmapState.mBitmap will be null. */ } finally { init(new BitmapState(bitmap), res); if (mBitmapState.mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); } } }
Returns the paint used to render this drawable.
/** * Returns the paint used to render this drawable. */
public final Paint getPaint() { return mBitmapState.mPaint; }
Returns the bitmap used by this drawable to render. May be null.
/** * Returns the bitmap used by this drawable to render. May be null. */
public final Bitmap getBitmap() { return mBitmapState.mBitmap; } private void computeBitmapSize() { final Bitmap bitmap = mBitmapState.mBitmap; if (bitmap != null) { mBitmapWidth = bitmap.getScaledWidth(mTargetDensity); mBitmapHeight = bitmap.getScaledHeight(mTargetDensity); } else { mBitmapWidth = mBitmapHeight = -1; } }
@hide
/** @hide */
public void setBitmap(Bitmap bitmap) { if (mBitmapState.mBitmap != bitmap) { mBitmapState.mBitmap = bitmap; computeBitmapSize(); invalidateSelf(); } }
Set the density scale at which this drawable will be rendered. This method assumes the drawable will be rendered at the same density as the specified canvas.
Params:
  • canvas – The Canvas from which the density scale must be obtained.
See Also:
/** * Set the density scale at which this drawable will be rendered. This * method assumes the drawable will be rendered at the same density as the * specified canvas. * * @param canvas The Canvas from which the density scale must be obtained. * * @see android.graphics.Bitmap#setDensity(int) * @see android.graphics.Bitmap#getDensity() */
public void setTargetDensity(Canvas canvas) { setTargetDensity(canvas.getDensity()); }
Set the density scale at which this drawable will be rendered.
Params:
  • metrics – The DisplayMetrics indicating the density scale for this drawable.
See Also:
/** * Set the density scale at which this drawable will be rendered. * * @param metrics The DisplayMetrics indicating the density scale for this drawable. * * @see android.graphics.Bitmap#setDensity(int) * @see android.graphics.Bitmap#getDensity() */
public void setTargetDensity(DisplayMetrics metrics) { setTargetDensity(metrics.densityDpi); }
Set the density at which this drawable will be rendered.
Params:
  • density – The density scale for this drawable.
See Also:
/** * Set the density at which this drawable will be rendered. * * @param density The density scale for this drawable. * * @see android.graphics.Bitmap#setDensity(int) * @see android.graphics.Bitmap#getDensity() */
public void setTargetDensity(int density) { if (mTargetDensity != density) { mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; if (mBitmapState.mBitmap != null) { computeBitmapSize(); } invalidateSelf(); } }
Get the gravity used to position/stretch the bitmap within its bounds. See android.view.Gravity
Returns:the gravity applied to the bitmap
/** Get the gravity used to position/stretch the bitmap within its bounds. * See android.view.Gravity * @return the gravity applied to the bitmap */
public int getGravity() { return mBitmapState.mGravity; }
Set the gravity used to position/stretch the bitmap within its bounds. See android.view.Gravity
Params:
  • gravity – the gravity
/** Set the gravity used to position/stretch the bitmap within its bounds. See android.view.Gravity * @param gravity the gravity */
public void setGravity(int gravity) { if (mBitmapState.mGravity != gravity) { mBitmapState.mGravity = gravity; mDstRectAndInsetsDirty = true; invalidateSelf(); } }
Enables or disables the mipmap hint for this drawable's bitmap. See Bitmap.setHasMipMap(boolean) for more information. If the bitmap is null calling this method has no effect.
Params:
  • mipMap – True if the bitmap should use mipmaps, false otherwise.
See Also:
/** * Enables or disables the mipmap hint for this drawable's bitmap. * See {@link Bitmap#setHasMipMap(boolean)} for more information. * * If the bitmap is null calling this method has no effect. * * @param mipMap True if the bitmap should use mipmaps, false otherwise. * * @see #hasMipMap() */
public void setMipMap(boolean mipMap) { if (mBitmapState.mBitmap != null) { mBitmapState.mBitmap.setHasMipMap(mipMap); invalidateSelf(); } }
Indicates whether the mipmap hint is enabled on this drawable's bitmap.
See Also:
Returns:True if the mipmap hint is set, false otherwise. If the bitmap is null, this method always returns false.
@attrref android.R.styleable#BitmapDrawable_mipMap
/** * Indicates whether the mipmap hint is enabled on this drawable's bitmap. * * @return True if the mipmap hint is set, false otherwise. If the bitmap * is null, this method always returns false. * * @see #setMipMap(boolean) * @attr ref android.R.styleable#BitmapDrawable_mipMap */
public boolean hasMipMap() { return mBitmapState.mBitmap != null && mBitmapState.mBitmap.hasMipMap(); }
Enables or disables anti-aliasing for this drawable. Anti-aliasing affects the edges of the bitmap only so it applies only when the drawable is rotated.
Params:
  • aa – True if the bitmap should be anti-aliased, false otherwise.
See Also:
/** * Enables or disables anti-aliasing for this drawable. Anti-aliasing affects * the edges of the bitmap only so it applies only when the drawable is rotated. * * @param aa True if the bitmap should be anti-aliased, false otherwise. * * @see #hasAntiAlias() */
public void setAntiAlias(boolean aa) { mBitmapState.mPaint.setAntiAlias(aa); invalidateSelf(); }
Indicates whether anti-aliasing is enabled for this drawable.
See Also:
Returns:True if anti-aliasing is enabled, false otherwise.
/** * Indicates whether anti-aliasing is enabled for this drawable. * * @return True if anti-aliasing is enabled, false otherwise. * * @see #setAntiAlias(boolean) */
public boolean hasAntiAlias() { return mBitmapState.mPaint.isAntiAlias(); } @Override public void setFilterBitmap(boolean filter) { mBitmapState.mPaint.setFilterBitmap(filter); invalidateSelf(); } @Override public boolean isFilterBitmap() { return mBitmapState.mPaint.isFilterBitmap(); } @Override public void setDither(boolean dither) { mBitmapState.mPaint.setDither(dither); invalidateSelf(); }
Indicates the repeat behavior of this drawable on the X axis.
Returns:TileMode.CLAMP if the bitmap does not repeat, TileMode.REPEAT or TileMode.MIRROR otherwise.
/** * Indicates the repeat behavior of this drawable on the X axis. * * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, * {@link android.graphics.Shader.TileMode#REPEAT} or * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. */
public Shader.TileMode getTileModeX() { return mBitmapState.mTileModeX; }
Indicates the repeat behavior of this drawable on the Y axis.
Returns:TileMode.CLAMP if the bitmap does not repeat, TileMode.REPEAT or TileMode.MIRROR otherwise.
/** * Indicates the repeat behavior of this drawable on the Y axis. * * @return {@link android.graphics.Shader.TileMode#CLAMP} if the bitmap does not repeat, * {@link android.graphics.Shader.TileMode#REPEAT} or * {@link android.graphics.Shader.TileMode#MIRROR} otherwise. */
public Shader.TileMode getTileModeY() { return mBitmapState.mTileModeY; }
Sets the repeat behavior of this drawable on the X axis. By default, the drawable does not repeat its bitmap. Using TileMode.REPEAT or TileMode.MIRROR the bitmap can be repeated (or tiled) if the bitmap is smaller than this drawable.
Params:
  • mode – The repeat mode for this drawable.
See Also:
@attrref android.R.styleable#BitmapDrawable_tileModeX
/** * Sets the repeat behavior of this drawable on the X axis. By default, the drawable * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) * if the bitmap is smaller than this drawable. * * @param mode The repeat mode for this drawable. * * @see #setTileModeY(android.graphics.Shader.TileMode) * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) * @attr ref android.R.styleable#BitmapDrawable_tileModeX */
public void setTileModeX(Shader.TileMode mode) { setTileModeXY(mode, mBitmapState.mTileModeY); }
Sets the repeat behavior of this drawable on the Y axis. By default, the drawable does not repeat its bitmap. Using TileMode.REPEAT or TileMode.MIRROR the bitmap can be repeated (or tiled) if the bitmap is smaller than this drawable.
Params:
  • mode – The repeat mode for this drawable.
See Also:
@attrref android.R.styleable#BitmapDrawable_tileModeY
/** * Sets the repeat behavior of this drawable on the Y axis. By default, the drawable * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) * if the bitmap is smaller than this drawable. * * @param mode The repeat mode for this drawable. * * @see #setTileModeX(android.graphics.Shader.TileMode) * @see #setTileModeXY(android.graphics.Shader.TileMode, android.graphics.Shader.TileMode) * @attr ref android.R.styleable#BitmapDrawable_tileModeY */
public final void setTileModeY(Shader.TileMode mode) { setTileModeXY(mBitmapState.mTileModeX, mode); }
Sets the repeat behavior of this drawable on both axis. By default, the drawable does not repeat its bitmap. Using TileMode.REPEAT or TileMode.MIRROR the bitmap can be repeated (or tiled) if the bitmap is smaller than this drawable.
Params:
  • xmode – The X repeat mode for this drawable.
  • ymode – The Y repeat mode for this drawable.
See Also:
/** * Sets the repeat behavior of this drawable on both axis. By default, the drawable * does not repeat its bitmap. Using {@link android.graphics.Shader.TileMode#REPEAT} or * {@link android.graphics.Shader.TileMode#MIRROR} the bitmap can be repeated (or tiled) * if the bitmap is smaller than this drawable. * * @param xmode The X repeat mode for this drawable. * @param ymode The Y repeat mode for this drawable. * * @see #setTileModeX(android.graphics.Shader.TileMode) * @see #setTileModeY(android.graphics.Shader.TileMode) */
public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) { final BitmapState state = mBitmapState; if (state.mTileModeX != xmode || state.mTileModeY != ymode) { state.mTileModeX = xmode; state.mTileModeY = ymode; state.mRebuildShader = true; mDstRectAndInsetsDirty = true; invalidateSelf(); } } @Override public void setAutoMirrored(boolean mirrored) { if (mBitmapState.mAutoMirrored != mirrored) { mBitmapState.mAutoMirrored = mirrored; invalidateSelf(); } } @Override public final boolean isAutoMirrored() { return mBitmapState.mAutoMirrored; } @Override public @Config int getChangingConfigurations() { return super.getChangingConfigurations() | mBitmapState.getChangingConfigurations(); } private boolean needMirroring() { return isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL; } @Override protected void onBoundsChange(Rect bounds) { mDstRectAndInsetsDirty = true; final Bitmap bitmap = mBitmapState.mBitmap; final Shader shader = mBitmapState.mPaint.getShader(); if (bitmap != null && shader != null) { updateShaderMatrix(bitmap, mBitmapState.mPaint, shader, needMirroring()); } } @Override public void draw(Canvas canvas) { final Bitmap bitmap = mBitmapState.mBitmap; if (bitmap == null) { return; } final BitmapState state = mBitmapState; final Paint paint = state.mPaint; if (state.mRebuildShader) { final Shader.TileMode tmx = state.mTileModeX; final Shader.TileMode tmy = state.mTileModeY; if (tmx == null && tmy == null) { paint.setShader(null); } else { paint.setShader(new BitmapShader(bitmap, tmx == null ? Shader.TileMode.CLAMP : tmx, tmy == null ? Shader.TileMode.CLAMP : tmy)); } state.mRebuildShader = false; } final int restoreAlpha; if (state.mBaseAlpha != 1.0f) { final Paint p = getPaint(); restoreAlpha = p.getAlpha(); p.setAlpha((int) (restoreAlpha * state.mBaseAlpha + 0.5f)); } else { restoreAlpha = -1; } final boolean clearColorFilter; if (mTintFilter != null && paint.getColorFilter() == null) { paint.setColorFilter(mTintFilter); clearColorFilter = true; } else { clearColorFilter = false; } updateDstRectAndInsetsIfDirty(); final Shader shader = paint.getShader(); final boolean needMirroring = needMirroring(); if (shader == null) { if (needMirroring) { canvas.save(); // Mirror the bitmap canvas.translate(mDstRect.right - mDstRect.left, 0); canvas.scale(-1.0f, 1.0f); } canvas.drawBitmap(bitmap, null, mDstRect, paint); if (needMirroring) { canvas.restore(); } } else { updateShaderMatrix(bitmap, paint, shader, needMirroring); canvas.drawRect(mDstRect, paint); } if (clearColorFilter) { paint.setColorFilter(null); } if (restoreAlpha >= 0) { paint.setAlpha(restoreAlpha); } }
Updates the paint's shader matrix to be consistent with the destination size and layout direction.
Params:
  • bitmap – the bitmap to be drawn
  • paint – the paint used to draw the bitmap
  • shader – the shader to set on the paint
  • needMirroring – whether the bitmap should be mirrored
/** * Updates the {@code paint}'s shader matrix to be consistent with the * destination size and layout direction. * * @param bitmap the bitmap to be drawn * @param paint the paint used to draw the bitmap * @param shader the shader to set on the paint * @param needMirroring whether the bitmap should be mirrored */
private void updateShaderMatrix(@NonNull Bitmap bitmap, @NonNull Paint paint, @NonNull Shader shader, boolean needMirroring) { final int sourceDensity = bitmap.getDensity(); final int targetDensity = mTargetDensity; final boolean needScaling = sourceDensity != 0 && sourceDensity != targetDensity; if (needScaling || needMirroring) { final Matrix matrix = getOrCreateMirrorMatrix(); matrix.reset(); if (needMirroring) { final int dx = mDstRect.right - mDstRect.left; matrix.setTranslate(dx, 0); matrix.setScale(-1, 1); } if (needScaling) { final float densityScale = targetDensity / (float) sourceDensity; matrix.postScale(densityScale, densityScale); } shader.setLocalMatrix(matrix); } else { mMirrorMatrix = null; shader.setLocalMatrix(Matrix.IDENTITY_MATRIX); } paint.setShader(shader); } private Matrix getOrCreateMirrorMatrix() { if (mMirrorMatrix == null) { mMirrorMatrix = new Matrix(); } return mMirrorMatrix; } private void updateDstRectAndInsetsIfDirty() { if (mDstRectAndInsetsDirty) { if (mBitmapState.mTileModeX == null && mBitmapState.mTileModeY == null) { final Rect bounds = getBounds(); final int layoutDirection = getLayoutDirection(); Gravity.apply(mBitmapState.mGravity, mBitmapWidth, mBitmapHeight, bounds, mDstRect, layoutDirection); final int left = mDstRect.left - bounds.left; final int top = mDstRect.top - bounds.top; final int right = bounds.right - mDstRect.right; final int bottom = bounds.bottom - mDstRect.bottom; mOpticalInsets = Insets.of(left, top, right, bottom); } else { copyBounds(mDstRect); mOpticalInsets = Insets.NONE; } } mDstRectAndInsetsDirty = false; }
@hide
/** * @hide */
@Override public Insets getOpticalInsets() { updateDstRectAndInsetsIfDirty(); return mOpticalInsets; } @Override public void getOutline(@NonNull Outline outline) { updateDstRectAndInsetsIfDirty(); outline.setRect(mDstRect); // Only opaque Bitmaps can report a non-0 alpha, // since only they are guaranteed to fill their bounds boolean opaqueOverShape = mBitmapState.mBitmap != null && !mBitmapState.mBitmap.hasAlpha(); outline.setAlpha(opaqueOverShape ? getAlpha() / 255.0f : 0.0f); } @Override public void setAlpha(int alpha) { final int oldAlpha = mBitmapState.mPaint.getAlpha(); if (alpha != oldAlpha) { mBitmapState.mPaint.setAlpha(alpha); invalidateSelf(); } } @Override public int getAlpha() { return mBitmapState.mPaint.getAlpha(); } @Override public void setColorFilter(ColorFilter colorFilter) { mBitmapState.mPaint.setColorFilter(colorFilter); invalidateSelf(); } @Override public ColorFilter getColorFilter() { return mBitmapState.mPaint.getColorFilter(); } @Override public void setTintList(ColorStateList tint) { final BitmapState state = mBitmapState; if (state.mTint != tint) { state.mTint = tint; mTintFilter = updateTintFilter(mTintFilter, tint, mBitmapState.mTintMode); invalidateSelf(); } } @Override public void setTintMode(PorterDuff.Mode tintMode) { final BitmapState state = mBitmapState; if (state.mTintMode != tintMode) { state.mTintMode = tintMode; mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, tintMode); invalidateSelf(); } }
@hideonly needed by a hack within ProgressBar
/** * @hide only needed by a hack within ProgressBar */
public ColorStateList getTint() { return mBitmapState.mTint; }
@hideonly needed by a hack within ProgressBar
/** * @hide only needed by a hack within ProgressBar */
public Mode getTintMode() { return mBitmapState.mTintMode; }
@hideCandidate for future API inclusion
/** * @hide Candidate for future API inclusion */
@Override public void setXfermode(Xfermode xfermode) { mBitmapState.mPaint.setXfermode(xfermode); invalidateSelf(); }
A mutable BitmapDrawable still shares its Bitmap with any other Drawable that comes from the same resource.
Returns:This drawable.
/** * A mutable BitmapDrawable still shares its Bitmap with any other Drawable * that comes from the same resource. * * @return This drawable. */
@Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { mBitmapState = new BitmapState(mBitmapState); mMutated = true; } return this; }
@hide
/** * @hide */
public void clearMutated() { super.clearMutated(); mMutated = false; } @Override protected boolean onStateChange(int[] stateSet) { final BitmapState state = mBitmapState; if (state.mTint != null && state.mTintMode != null) { mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode); return true; } return false; } @Override public boolean isStateful() { return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful()) || super.isStateful(); }
@hide
/** @hide */
@Override public boolean hasFocusStateSpecified() { return mBitmapState.mTint != null && mBitmapState.mTint.hasFocusStateSpecified(); } @Override public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { super.inflate(r, parser, attrs, theme); final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.BitmapDrawable); updateStateFromTypedArray(a, mSrcDensityOverride); verifyRequiredAttributes(a); a.recycle(); // Update local properties. updateLocalState(r); }
Ensures all required attributes are set.
Throws:
  • XmlPullParserException – if any required attributes are missing
/** * Ensures all required attributes are set. * * @throws XmlPullParserException if any required attributes are missing */
private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { // If we're not waiting on a theme, verify required attributes. final BitmapState state = mBitmapState; if (state.mBitmap == null && (state.mThemeAttrs == null || state.mThemeAttrs[R.styleable.BitmapDrawable_src] == 0)) { throw new XmlPullParserException(a.getPositionDescription() + ": <bitmap> requires a valid 'src' attribute"); } }
Updates the constant state from the values in the typed array.
/** * Updates the constant state from the values in the typed array. */
private void updateStateFromTypedArray(TypedArray a, int srcDensityOverride) throws XmlPullParserException { final Resources r = a.getResources(); final BitmapState state = mBitmapState; // Account for any configuration changes. state.mChangingConfigurations |= a.getChangingConfigurations(); // Extract the theme attributes, if any. state.mThemeAttrs = a.extractThemeAttrs(); state.mSrcDensityOverride = srcDensityOverride; state.mTargetDensity = Drawable.resolveDensity(r, 0); final int srcResId = a.getResourceId(R.styleable.BitmapDrawable_src, 0); if (srcResId != 0) { final TypedValue value = new TypedValue(); r.getValueForDensity(srcResId, srcDensityOverride, value, true); // Pretend the requested density is actually the display density. If // the drawable returned is not the requested density, then force it // to be scaled later by dividing its density by the ratio of // requested density to actual device density. Drawables that have // undefined density or no density don't need to be handled here. if (srcDensityOverride > 0 && value.density > 0 && value.density != TypedValue.DENSITY_NONE) { if (value.density == srcDensityOverride) { value.density = r.getDisplayMetrics().densityDpi; } else { value.density = (value.density * r.getDisplayMetrics().densityDpi) / srcDensityOverride; } } int density = Bitmap.DENSITY_NONE; if (value.density == TypedValue.DENSITY_DEFAULT) { density = DisplayMetrics.DENSITY_DEFAULT; } else if (value.density != TypedValue.DENSITY_NONE) { density = value.density; } Bitmap bitmap = null; try (InputStream is = r.openRawResource(srcResId, value)) { ImageDecoder.Source source = ImageDecoder.createSource(r, is, density); bitmap = ImageDecoder.decodeBitmap(source, (decoder, info, src) -> { decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE); }); } catch (Exception e) { // Do nothing and pick up the error below. } if (bitmap == null) { throw new XmlPullParserException(a.getPositionDescription() + ": <bitmap> requires a valid 'src' attribute"); } state.mBitmap = bitmap; } final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; setMipMap(a.getBoolean(R.styleable.BitmapDrawable_mipMap, defMipMap)); state.mAutoMirrored = a.getBoolean( R.styleable.BitmapDrawable_autoMirrored, state.mAutoMirrored); state.mBaseAlpha = a.getFloat(R.styleable.BitmapDrawable_alpha, state.mBaseAlpha); final int tintMode = a.getInt(R.styleable.BitmapDrawable_tintMode, -1); if (tintMode != -1) { state.mTintMode = Drawable.parseTintMode(tintMode, Mode.SRC_IN); } final ColorStateList tint = a.getColorStateList(R.styleable.BitmapDrawable_tint); if (tint != null) { state.mTint = tint; } final Paint paint = mBitmapState.mPaint; paint.setAntiAlias(a.getBoolean( R.styleable.BitmapDrawable_antialias, paint.isAntiAlias())); paint.setFilterBitmap(a.getBoolean( R.styleable.BitmapDrawable_filter, paint.isFilterBitmap())); paint.setDither(a.getBoolean(R.styleable.BitmapDrawable_dither, paint.isDither())); setGravity(a.getInt(R.styleable.BitmapDrawable_gravity, state.mGravity)); final int tileMode = a.getInt(R.styleable.BitmapDrawable_tileMode, TILE_MODE_UNDEFINED); if (tileMode != TILE_MODE_UNDEFINED) { final Shader.TileMode mode = parseTileMode(tileMode); setTileModeXY(mode, mode); } final int tileModeX = a.getInt(R.styleable.BitmapDrawable_tileModeX, TILE_MODE_UNDEFINED); if (tileModeX != TILE_MODE_UNDEFINED) { setTileModeX(parseTileMode(tileModeX)); } final int tileModeY = a.getInt(R.styleable.BitmapDrawable_tileModeY, TILE_MODE_UNDEFINED); if (tileModeY != TILE_MODE_UNDEFINED) { setTileModeY(parseTileMode(tileModeY)); } } @Override public void applyTheme(Theme t) { super.applyTheme(t); final BitmapState state = mBitmapState; if (state == null) { return; } if (state.mThemeAttrs != null) { final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.BitmapDrawable); try { updateStateFromTypedArray(a, state.mSrcDensityOverride); } catch (XmlPullParserException e) { rethrowAsRuntimeException(e); } finally { a.recycle(); } } // Apply theme to contained color state list. if (state.mTint != null && state.mTint.canApplyTheme()) { state.mTint = state.mTint.obtainForTheme(t); } // Update local properties. updateLocalState(t.getResources()); } private static Shader.TileMode parseTileMode(int tileMode) { switch (tileMode) { case TILE_MODE_CLAMP: return Shader.TileMode.CLAMP; case TILE_MODE_REPEAT: return Shader.TileMode.REPEAT; case TILE_MODE_MIRROR: return Shader.TileMode.MIRROR; default: return null; } } @Override public boolean canApplyTheme() { return mBitmapState != null && mBitmapState.canApplyTheme(); } @Override public int getIntrinsicWidth() { return mBitmapWidth; } @Override public int getIntrinsicHeight() { return mBitmapHeight; } @Override public int getOpacity() { if (mBitmapState.mGravity != Gravity.FILL) { return PixelFormat.TRANSLUCENT; } final Bitmap bitmap = mBitmapState.mBitmap; return (bitmap == null || bitmap.hasAlpha() || mBitmapState.mPaint.getAlpha() < 255) ? PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; } @Override public final ConstantState getConstantState() { mBitmapState.mChangingConfigurations |= getChangingConfigurations(); return mBitmapState; } final static class BitmapState extends ConstantState { final Paint mPaint; // Values loaded during inflation. int[] mThemeAttrs = null; Bitmap mBitmap = null; ColorStateList mTint = null; Mode mTintMode = DEFAULT_TINT_MODE; int mGravity = Gravity.FILL; float mBaseAlpha = 1.0f; Shader.TileMode mTileModeX = null; Shader.TileMode mTileModeY = null; // The density to use when looking up the bitmap in Resources. A value of 0 means use // the system's density. int mSrcDensityOverride = 0; // The density at which to render the bitmap. int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; boolean mAutoMirrored = false; @Config int mChangingConfigurations; boolean mRebuildShader; BitmapState(Bitmap bitmap) { mBitmap = bitmap; mPaint = new Paint(DEFAULT_PAINT_FLAGS); } BitmapState(BitmapState bitmapState) { mBitmap = bitmapState.mBitmap; mTint = bitmapState.mTint; mTintMode = bitmapState.mTintMode; mThemeAttrs = bitmapState.mThemeAttrs; mChangingConfigurations = bitmapState.mChangingConfigurations; mGravity = bitmapState.mGravity; mTileModeX = bitmapState.mTileModeX; mTileModeY = bitmapState.mTileModeY; mSrcDensityOverride = bitmapState.mSrcDensityOverride; mTargetDensity = bitmapState.mTargetDensity; mBaseAlpha = bitmapState.mBaseAlpha; mPaint = new Paint(bitmapState.mPaint); mRebuildShader = bitmapState.mRebuildShader; mAutoMirrored = bitmapState.mAutoMirrored; } @Override public boolean canApplyTheme() { return mThemeAttrs != null || mTint != null && mTint.canApplyTheme(); } @Override public Drawable newDrawable() { return new BitmapDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { return new BitmapDrawable(this, res); } @Override public @Config int getChangingConfigurations() { return mChangingConfigurations | (mTint != null ? mTint.getChangingConfigurations() : 0); } } private BitmapDrawable(BitmapState state, Resources res) { init(state, res); }
The one helper to rule them all. This is called by all public & private constructors to set the state and initialize local properties.
/** * The one helper to rule them all. This is called by all public & private * constructors to set the state and initialize local properties. */
private void init(BitmapState state, Resources res) { mBitmapState = state; updateLocalState(res); if (mBitmapState != null && res != null) { mBitmapState.mTargetDensity = mTargetDensity; } }
Initializes local dynamic properties from state. This should be called after significant state changes, e.g. from the One True Constructor and after inflating or applying a theme.
/** * Initializes local dynamic properties from state. This should be called * after significant state changes, e.g. from the One True Constructor and * after inflating or applying a theme. */
private void updateLocalState(Resources res) { mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity); mTintFilter = updateTintFilter(mTintFilter, mBitmapState.mTint, mBitmapState.mTintMode); computeBitmapSize(); } }