There was a lot of fancy code just above the clear to ensure that drawables that aren't affected by the change are kept, then the entire array was cleared. This patch removes the clear, so that the drawables that haven't changed are really kept, matching the logs, comments and larger part of the code. This patch also fixes the various constant states to return correct ChangingConfigurations. Change-Id: Ic11f6179537318d3de16dc58286989eb62a07f15 Old-Change-Id: I22495e6ed232dfe056207ce5155405af1fa82428
485 lines
16 KiB
Java
485 lines
16 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.content.res.Resources;
|
|
import android.content.res.TypedArray;
|
|
import android.graphics.Bitmap;
|
|
import android.graphics.BitmapFactory;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.ColorFilter;
|
|
import android.graphics.Paint;
|
|
import android.graphics.PixelFormat;
|
|
import android.graphics.Rect;
|
|
import android.graphics.Shader;
|
|
import android.graphics.BitmapShader;
|
|
import android.util.AttributeSet;
|
|
import android.util.DisplayMetrics;
|
|
import android.view.Gravity;
|
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
import java.io.IOException;
|
|
|
|
/**
|
|
* 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_tileMode
|
|
*/
|
|
public class BitmapDrawable extends Drawable {
|
|
|
|
private static final int DEFAULT_PAINT_FLAGS =
|
|
Paint.FILTER_BITMAP_FLAG | Paint.DITHER_FLAG;
|
|
private BitmapState mBitmapState;
|
|
private Bitmap mBitmap;
|
|
private int mTargetDensity;
|
|
|
|
private final Rect mDstRect = new Rect(); // Gravity.apply() sets this
|
|
|
|
private boolean mApplyGravity;
|
|
private boolean mRebuildShader;
|
|
private boolean mMutated;
|
|
|
|
// These are scaled to match the target density.
|
|
private int mBitmapWidth;
|
|
private int mBitmapHeight;
|
|
|
|
/**
|
|
* Create an empty drawable, not dealing with density.
|
|
* @deprecated Use {@link #BitmapDrawable(Resources)} to ensure
|
|
* that the drawable has correctly set its target density.
|
|
*/
|
|
@Deprecated
|
|
public BitmapDrawable() {
|
|
mBitmapState = new BitmapState((Bitmap) null);
|
|
}
|
|
|
|
/**
|
|
* Create an empty drawable, setting initial target density based on
|
|
* the display metrics of the resources.
|
|
*/
|
|
public BitmapDrawable(Resources res) {
|
|
mBitmapState = new BitmapState((Bitmap) null);
|
|
mBitmapState.mTargetDensity = mTargetDensity;
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
this(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) {
|
|
this(new BitmapState(bitmap), res);
|
|
mBitmapState.mTargetDensity = mTargetDensity;
|
|
}
|
|
|
|
/**
|
|
* 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(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
|
|
if (mBitmap == null) {
|
|
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a drawable by opening a given file path and decoding the bitmap.
|
|
*/
|
|
public BitmapDrawable(Resources res, String filepath) {
|
|
this(new BitmapState(BitmapFactory.decodeFile(filepath)), null);
|
|
mBitmapState.mTargetDensity = mTargetDensity;
|
|
if (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 {@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(new BitmapState(BitmapFactory.decodeStream(is)), null);
|
|
if (mBitmap == null) {
|
|
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a drawable by decoding a bitmap from the given input stream.
|
|
*/
|
|
public BitmapDrawable(Resources res, java.io.InputStream is) {
|
|
this(new BitmapState(BitmapFactory.decodeStream(is)), null);
|
|
mBitmapState.mTargetDensity = mTargetDensity;
|
|
if (mBitmap == null) {
|
|
android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
|
|
}
|
|
}
|
|
|
|
public final Paint getPaint() {
|
|
return mBitmapState.mPaint;
|
|
}
|
|
|
|
public final Bitmap getBitmap() {
|
|
return mBitmap;
|
|
}
|
|
|
|
private void computeBitmapSize() {
|
|
mBitmapWidth = mBitmap.getScaledWidth(mTargetDensity);
|
|
mBitmapHeight = mBitmap.getScaledHeight(mTargetDensity);
|
|
}
|
|
|
|
private void setBitmap(Bitmap bitmap) {
|
|
mBitmap = bitmap;
|
|
if (bitmap != null) {
|
|
computeBitmapSize();
|
|
} else {
|
|
mBitmapWidth = mBitmapHeight = -1;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
mTargetDensity = metrics.densityDpi;
|
|
if (mBitmap != null) {
|
|
computeBitmapSize();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) {
|
|
mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
|
|
if (mBitmap != null) {
|
|
computeBitmapSize();
|
|
}
|
|
}
|
|
|
|
/** 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) {
|
|
mBitmapState.mGravity = gravity;
|
|
mApplyGravity = true;
|
|
}
|
|
|
|
public void setAntiAlias(boolean aa) {
|
|
mBitmapState.mPaint.setAntiAlias(aa);
|
|
}
|
|
|
|
@Override
|
|
public void setFilterBitmap(boolean filter) {
|
|
mBitmapState.mPaint.setFilterBitmap(filter);
|
|
}
|
|
|
|
@Override
|
|
public void setDither(boolean dither) {
|
|
mBitmapState.mPaint.setDither(dither);
|
|
}
|
|
|
|
public Shader.TileMode getTileModeX() {
|
|
return mBitmapState.mTileModeX;
|
|
}
|
|
|
|
public Shader.TileMode getTileModeY() {
|
|
return mBitmapState.mTileModeY;
|
|
}
|
|
|
|
public void setTileModeX(Shader.TileMode mode) {
|
|
setTileModeXY(mode, mBitmapState.mTileModeY);
|
|
}
|
|
|
|
public final void setTileModeY(Shader.TileMode mode) {
|
|
setTileModeXY(mBitmapState.mTileModeX, mode);
|
|
}
|
|
|
|
public void setTileModeXY(Shader.TileMode xmode, Shader.TileMode ymode) {
|
|
final BitmapState state = mBitmapState;
|
|
if (state.mPaint.getShader() == null ||
|
|
state.mTileModeX != xmode || state.mTileModeY != ymode) {
|
|
state.mTileModeX = xmode;
|
|
state.mTileModeY = ymode;
|
|
mRebuildShader = true;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int getChangingConfigurations() {
|
|
return super.getChangingConfigurations() | mBitmapState.mChangingConfigurations;
|
|
}
|
|
|
|
@Override
|
|
protected void onBoundsChange(Rect bounds) {
|
|
super.onBoundsChange(bounds);
|
|
mApplyGravity = true;
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas canvas) {
|
|
Bitmap bitmap = mBitmap;
|
|
if (bitmap != null) {
|
|
final BitmapState state = mBitmapState;
|
|
if (mRebuildShader) {
|
|
Shader.TileMode tmx = state.mTileModeX;
|
|
Shader.TileMode tmy = state.mTileModeY;
|
|
|
|
if (tmx == null && tmy == null) {
|
|
state.mPaint.setShader(null);
|
|
} else {
|
|
Shader s = new BitmapShader(bitmap,
|
|
tmx == null ? Shader.TileMode.CLAMP : tmx,
|
|
tmy == null ? Shader.TileMode.CLAMP : tmy);
|
|
state.mPaint.setShader(s);
|
|
}
|
|
mRebuildShader = false;
|
|
copyBounds(mDstRect);
|
|
}
|
|
|
|
Shader shader = state.mPaint.getShader();
|
|
if (shader == null) {
|
|
if (mApplyGravity) {
|
|
Gravity.apply(state.mGravity, mBitmapWidth, mBitmapHeight,
|
|
getBounds(), mDstRect);
|
|
mApplyGravity = false;
|
|
}
|
|
canvas.drawBitmap(bitmap, null, mDstRect, state.mPaint);
|
|
} else {
|
|
if (mApplyGravity) {
|
|
mDstRect.set(getBounds());
|
|
mApplyGravity = false;
|
|
}
|
|
canvas.drawRect(mDstRect, state.mPaint);
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setAlpha(int alpha) {
|
|
mBitmapState.mPaint.setAlpha(alpha);
|
|
}
|
|
|
|
@Override
|
|
public void setColorFilter(ColorFilter cf) {
|
|
mBitmapState.mPaint.setColorFilter(cf);
|
|
}
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
@Override
|
|
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
|
|
throws XmlPullParserException, IOException {
|
|
super.inflate(r, parser, attrs);
|
|
|
|
TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable);
|
|
|
|
final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0);
|
|
if (id == 0) {
|
|
throw new XmlPullParserException(parser.getPositionDescription() +
|
|
": <bitmap> requires a valid src attribute");
|
|
}
|
|
final Bitmap bitmap = BitmapFactory.decodeResource(r, id);
|
|
if (bitmap == null) {
|
|
throw new XmlPullParserException(parser.getPositionDescription() +
|
|
": <bitmap> requires a valid src attribute");
|
|
}
|
|
mBitmapState.mBitmap = bitmap;
|
|
setBitmap(bitmap);
|
|
setTargetDensity(r.getDisplayMetrics());
|
|
|
|
final Paint paint = mBitmapState.mPaint;
|
|
paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias,
|
|
paint.isAntiAlias()));
|
|
paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter,
|
|
paint.isFilterBitmap()));
|
|
paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither,
|
|
paint.isDither()));
|
|
setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL));
|
|
int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1);
|
|
if (tileMode != -1) {
|
|
switch (tileMode) {
|
|
case 0:
|
|
setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
|
|
break;
|
|
case 1:
|
|
setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
|
|
break;
|
|
case 2:
|
|
setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);
|
|
break;
|
|
}
|
|
}
|
|
|
|
a.recycle();
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicWidth() {
|
|
return mBitmapWidth;
|
|
}
|
|
|
|
@Override
|
|
public int getIntrinsicHeight() {
|
|
return mBitmapHeight;
|
|
}
|
|
|
|
@Override
|
|
public int getOpacity() {
|
|
if (mBitmapState.mGravity != Gravity.FILL) {
|
|
return PixelFormat.TRANSLUCENT;
|
|
}
|
|
Bitmap bm = mBitmap;
|
|
return (bm == null || bm.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 {
|
|
Bitmap mBitmap;
|
|
int mChangingConfigurations;
|
|
int mGravity = Gravity.FILL;
|
|
Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS);
|
|
Shader.TileMode mTileModeX;
|
|
Shader.TileMode mTileModeY;
|
|
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
|
|
|
|
BitmapState(Bitmap bitmap) {
|
|
mBitmap = bitmap;
|
|
}
|
|
|
|
BitmapState(BitmapState bitmapState) {
|
|
this(bitmapState.mBitmap);
|
|
mChangingConfigurations = bitmapState.mChangingConfigurations;
|
|
mGravity = bitmapState.mGravity;
|
|
mTileModeX = bitmapState.mTileModeX;
|
|
mTileModeY = bitmapState.mTileModeY;
|
|
mTargetDensity = bitmapState.mTargetDensity;
|
|
mPaint = new Paint(bitmapState.mPaint);
|
|
}
|
|
|
|
@Override
|
|
public Drawable newDrawable() {
|
|
return new BitmapDrawable(this, null);
|
|
}
|
|
|
|
@Override
|
|
public Drawable newDrawable(Resources res) {
|
|
return new BitmapDrawable(this, res);
|
|
}
|
|
|
|
@Override
|
|
public int getChangingConfigurations() {
|
|
return mChangingConfigurations;
|
|
}
|
|
}
|
|
|
|
private BitmapDrawable(BitmapState state, Resources res) {
|
|
mBitmapState = state;
|
|
if (res != null) {
|
|
mTargetDensity = res.getDisplayMetrics().densityDpi;
|
|
} else if (state != null) {
|
|
mTargetDensity = state.mTargetDensity;
|
|
} else {
|
|
mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
|
|
}
|
|
setBitmap(state.mBitmap);
|
|
}
|
|
}
|