1086 lines
37 KiB
Java
1086 lines
37 KiB
Java
![]() |
/*
|
||
|
* 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.annotation.Nullable;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
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.BitmapShader;
|
||
|
import android.graphics.BlendMode;
|
||
|
import android.graphics.BlendModeColorFilter;
|
||
|
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.Mode;
|
||
|
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.Log;
|
||
|
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 {@link android.graphics.Bitmap} object.
|
||
|
* <p>It can be defined in an XML file with the <code><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
|
||
|
|
||
|
@UnsupportedAppUsage
|
||
|
private BitmapState mBitmapState;
|
||
|
private BlendModeColorFilter mBlendModeFilter;
|
||
|
|
||
|
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. */
|
||
|
private Insets mOpticalInsets = Insets.NONE;
|
||
|
|
||
|
// Mirroring matrix for using with Shaders
|
||
|
private Matrix mMirrorMatrix;
|
||
|
|
||
|
/**
|
||
|
* 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 {@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 {@link #BitmapDrawable(Resources, Bitmap)} to ensure
|
||
|
* that the drawable has correctly set its target density.
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public BitmapDrawable(Bitmap bitmap) {
|
||
|
if (bitmap == null) {
|
||
|
Log.w(TAG, "BitmapDrawable created with null Bitmap");
|
||
|
}
|
||
|
init(new BitmapState(bitmap), null);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Create drawable from a bitmap, setting initial target density based on
|
||
|
* the display metrics of the resources.
|
||
|
*/
|
||
|
public BitmapDrawable(Resources res, Bitmap bitmap) {
|
||
|
if (bitmap == null) {
|
||
|
Log.w(TAG, "BitmapDrawable created with null Bitmap");
|
||
|
}
|
||
|
init(new BitmapState(bitmap), res);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
@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) {
|
||
|
Log.w(TAG, "BitmapDrawable cannot decode " + filepath);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
@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) {
|
||
|
Log.w(TAG, "BitmapDrawable cannot decode " + is);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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.
|
||
|
*/
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Switch to a new Bitmap object.
|
||
|
*/
|
||
|
public void setBitmap(@Nullable 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.
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* @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
|
||
|
* @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
|
||
|
* @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 {@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.
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* @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.
|
||
|
*
|
||
|
* @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 {@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 {@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 {@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 (mBlendModeFilter != null && paint.getColorFilter() == null) {
|
||
|
paint.setColorFilter(mBlendModeFilter);
|
||
|
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 {@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;
|
||
|
}
|
||
|
|
||
|
@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;
|
||
|
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, tint,
|
||
|
mBitmapState.mBlendMode);
|
||
|
invalidateSelf();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public void setTintBlendMode(@NonNull BlendMode blendMode) {
|
||
|
final BitmapState state = mBitmapState;
|
||
|
if (state.mBlendMode != blendMode) {
|
||
|
state.mBlendMode = blendMode;
|
||
|
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
|
||
|
blendMode);
|
||
|
invalidateSelf();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* No longer needed by ProgressBar, but still here due to UnsupportedAppUsage.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
private ColorStateList getTint() {
|
||
|
return mBitmapState.mTint;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* No longer needed by ProgressBar, but still here due to UnsupportedAppUsage.
|
||
|
*/
|
||
|
@UnsupportedAppUsage
|
||
|
private Mode getTintMode() {
|
||
|
return BlendMode.blendModeToPorterDuffMode(mBitmapState.mBlendMode);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @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.
|
||
|
*
|
||
|
* @return This drawable.
|
||
|
*/
|
||
|
@Override
|
||
|
public Drawable mutate() {
|
||
|
if (!mMutated && super.mutate() == this) {
|
||
|
mBitmapState = new BitmapState(mBitmapState);
|
||
|
mMutated = true;
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @hide
|
||
|
*/
|
||
|
public void clearMutated() {
|
||
|
super.clearMutated();
|
||
|
mMutated = false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected boolean onStateChange(int[] stateSet) {
|
||
|
final BitmapState state = mBitmapState;
|
||
|
if (state.mTint != null && state.mBlendMode != null) {
|
||
|
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, state.mTint,
|
||
|
state.mBlendMode);
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
public boolean isStateful() {
|
||
|
return (mBitmapState.mTint != null && mBitmapState.mTint.isStateful())
|
||
|
|| super.isStateful();
|
||
|
}
|
||
|
|
||
|
@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
|
||
|
*/
|
||
|
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.
|
||
|
*/
|
||
|
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.mBlendMode = Drawable.parseBlendMode(tintMode, BlendMode.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;
|
||
|
BlendMode mBlendMode = DEFAULT_BLEND_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;
|
||
|
mBlendMode = bitmapState.mBlendMode;
|
||
|
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.
|
||
|
*/
|
||
|
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.
|
||
|
*/
|
||
|
private void updateLocalState(Resources res) {
|
||
|
mTargetDensity = resolveDensity(res, mBitmapState.mTargetDensity);
|
||
|
mBlendModeFilter = updateBlendModeFilter(mBlendModeFilter, mBitmapState.mTint,
|
||
|
mBitmapState.mBlendMode);
|
||
|
computeBitmapSize();
|
||
|
}
|
||
|
|
||
|
private static final String TAG = "BitmapDrawable";
|
||
|
}
|