Files
frameworks_base/graphics/java/android/graphics/drawable/NinePatchDrawable.java
Dianne Hackborn 908aecc3a6 Start moving away from DisplayMetrics.DENSITY_DEVICE.
This puts in most of the infrastructure needed to allow us to
switch between different densities at run time.  The main remaining
uses of the global are to initialize the Bitmap object (not sure
what to do about that since it doesn't have anything passed in
the constructor to get this information from), and being able to
load drawables if we need a different density than what was preloaded
by zygote.

Change-Id: Ifdbfd6b7a5c59e6aa22e63b95b78d96af3d96848
2012-08-01 10:54:39 -07:00

452 lines
15 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.Insets;
import android.graphics.NinePatch;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.io.InputStream;
/**
*
* A resizeable bitmap, with stretchable areas that you define. This type of image
* is defined in a .png file with a special format.
*
* <div class="special reference">
* <h3>Developer Guides</h3>
* <p>For more information about how to use a NinePatchDrawable, read the
* <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch">
* Canvas and Drawables</a> developer guide. For information about creating a NinePatch image
* file using the draw9patch tool, see the
* <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div>
*/
public class NinePatchDrawable extends Drawable {
// dithering helps a lot, and is pretty cheap, so default is true
private static final boolean DEFAULT_DITHER = true;
private NinePatchState mNinePatchState;
private NinePatch mNinePatch;
private Rect mPadding;
private Insets mLayoutInsets = Insets.NONE;
private Paint mPaint;
private boolean mMutated;
private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
// These are scaled to match the target density.
private int mBitmapWidth;
private int mBitmapHeight;
NinePatchDrawable() {
}
/**
* Create drawable from raw nine-patch data, not dealing with density.
* @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)}
* to ensure that the drawable has correctly set its target density.
*/
@Deprecated
public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
}
/**
* Create drawable from raw nine-patch data, setting initial target density
* based on the display metrics of the resources.
*/
public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
Rect padding, String srcName) {
this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
mNinePatchState.mTargetDensity = mTargetDensity;
}
/**
* Create drawable from raw nine-patch data, setting initial target density
* based on the display metrics of the resources.
*
* @hide
*/
public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk,
Rect padding, Rect layoutInsets, String srcName) {
this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, layoutInsets), res);
mNinePatchState.mTargetDensity = mTargetDensity;
}
/**
* Create drawable from existing nine-patch, not dealing with density.
* @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)}
* to ensure that the drawable has correctly set its target density.
*/
@Deprecated
public NinePatchDrawable(NinePatch patch) {
this(new NinePatchState(patch, new Rect()), null);
}
/**
* Create drawable from existing nine-patch, setting initial target density
* based on the display metrics of the resources.
*/
public NinePatchDrawable(Resources res, NinePatch patch) {
this(new NinePatchState(patch, new Rect()), res);
mNinePatchState.mTargetDensity = mTargetDensity;
}
private void setNinePatchState(NinePatchState state, Resources res) {
mNinePatchState = state;
mNinePatch = state.mNinePatch;
mPadding = state.mPadding;
mTargetDensity = res != null ? res.getDisplayMetrics().densityDpi
: state.mTargetDensity;
//noinspection PointlessBooleanExpression
if (state.mDither != DEFAULT_DITHER) {
// avoid calling the setter unless we need to, since it does a
// lazy allocation of a paint
setDither(state.mDither);
}
if (mNinePatch != null) {
computeBitmapSize();
}
}
/**
* 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 (density != mTargetDensity) {
mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density;
if (mNinePatch != null) {
computeBitmapSize();
}
invalidateSelf();
}
}
private Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) {
int left = Bitmap.scaleFromDensity(insets.left, sdensity, tdensity);
int top = Bitmap.scaleFromDensity(insets.top, sdensity, tdensity);
int right = Bitmap.scaleFromDensity(insets.right, sdensity, tdensity);
int bottom = Bitmap.scaleFromDensity(insets.bottom, sdensity, tdensity);
return Insets.of(left, top, right, bottom);
}
private void computeBitmapSize() {
final int sdensity = mNinePatch.getDensity();
final int tdensity = mTargetDensity;
if (sdensity == tdensity) {
mBitmapWidth = mNinePatch.getWidth();
mBitmapHeight = mNinePatch.getHeight();
mLayoutInsets = mNinePatchState.mLayoutInsets;
} else {
mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(),
sdensity, tdensity);
mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(),
sdensity, tdensity);
if (mNinePatchState.mPadding != null && mPadding != null) {
Rect dest = mPadding;
Rect src = mNinePatchState.mPadding;
if (dest == src) {
mPadding = dest = new Rect(src);
}
dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity);
dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity);
dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity);
dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity);
}
mLayoutInsets = scaleFromDensity(mNinePatchState.mLayoutInsets, sdensity, tdensity);
}
}
@Override
public void draw(Canvas canvas) {
mNinePatch.draw(canvas, getBounds(), mPaint);
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations;
}
@Override
public boolean getPadding(Rect padding) {
padding.set(mPadding);
return true;
}
/**
* @hide
*/
@Override
public Insets getLayoutInsets() {
return mLayoutInsets;
}
@Override
public void setAlpha(int alpha) {
if (mPaint == null && alpha == 0xFF) {
// Fast common case -- leave at normal alpha.
return;
}
getPaint().setAlpha(alpha);
invalidateSelf();
}
@Override
public void setColorFilter(ColorFilter cf) {
if (mPaint == null && cf == null) {
// Fast common case -- leave at no color filter.
return;
}
getPaint().setColorFilter(cf);
invalidateSelf();
}
@Override
public void setDither(boolean dither) {
if (mPaint == null && dither == DEFAULT_DITHER) {
// Fast common case -- leave at default dither.
return;
}
getPaint().setDither(dither);
invalidateSelf();
}
@Override
public void setFilterBitmap(boolean filter) {
getPaint().setFilterBitmap(filter);
invalidateSelf();
}
@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.NinePatchDrawable);
final int id = a.getResourceId(com.android.internal.R.styleable.NinePatchDrawable_src, 0);
if (id == 0) {
throw new XmlPullParserException(parser.getPositionDescription() +
": <nine-patch> requires a valid src attribute");
}
final boolean dither = a.getBoolean(
com.android.internal.R.styleable.NinePatchDrawable_dither,
DEFAULT_DITHER);
final BitmapFactory.Options options = new BitmapFactory.Options();
if (dither) {
options.inDither = false;
}
options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi;
final Rect padding = new Rect();
final Rect layoutInsets = new Rect();
Bitmap bitmap = null;
try {
final TypedValue value = new TypedValue();
final InputStream is = r.openRawResource(id, value);
bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
is.close();
} catch (IOException e) {
// Ignore
}
if (bitmap == null) {
throw new XmlPullParserException(parser.getPositionDescription() +
": <nine-patch> requires a valid src attribute");
} else if (bitmap.getNinePatchChunk() == null) {
throw new XmlPullParserException(parser.getPositionDescription() +
": <nine-patch> requires a valid 9-patch source image");
}
setNinePatchState(new NinePatchState(
new NinePatch(bitmap, bitmap.getNinePatchChunk(), "XML 9-patch"),
padding, layoutInsets, dither), r);
mNinePatchState.mTargetDensity = mTargetDensity;
a.recycle();
}
public Paint getPaint() {
if (mPaint == null) {
mPaint = new Paint();
mPaint.setDither(DEFAULT_DITHER);
}
return mPaint;
}
/**
* Retrieves the width of the source .png file (before resizing).
*/
@Override
public int getIntrinsicWidth() {
return mBitmapWidth;
}
/**
* Retrieves the height of the source .png file (before resizing).
*/
@Override
public int getIntrinsicHeight() {
return mBitmapHeight;
}
@Override
public int getMinimumWidth() {
return mBitmapWidth;
}
@Override
public int getMinimumHeight() {
return mBitmapHeight;
}
/**
* Returns a {@link android.graphics.PixelFormat graphics.PixelFormat}
* value of OPAQUE or TRANSLUCENT.
*/
@Override
public int getOpacity() {
return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ?
PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE;
}
@Override
public Region getTransparentRegion() {
return mNinePatch.getTransparentRegion(getBounds());
}
@Override
public ConstantState getConstantState() {
mNinePatchState.mChangingConfigurations = getChangingConfigurations();
return mNinePatchState;
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mNinePatchState = new NinePatchState(mNinePatchState);
mNinePatch = mNinePatchState.mNinePatch;
mMutated = true;
}
return this;
}
private final static class NinePatchState extends ConstantState {
final NinePatch mNinePatch;
final Rect mPadding;
final Insets mLayoutInsets;
final boolean mDither;
int mChangingConfigurations;
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
NinePatchState(NinePatch ninePatch, Rect padding) {
this(ninePatch, padding, new Rect(), DEFAULT_DITHER);
}
NinePatchState(NinePatch ninePatch, Rect padding, Rect layoutInsets) {
this(ninePatch, padding, layoutInsets, DEFAULT_DITHER);
}
NinePatchState(NinePatch ninePatch, Rect rect, Rect layoutInsets, boolean dither) {
mNinePatch = ninePatch;
mPadding = rect;
mLayoutInsets = Insets.of(layoutInsets);
mDither = dither;
}
// Copy constructor
NinePatchState(NinePatchState state) {
mNinePatch = new NinePatch(state.mNinePatch);
// Note we don't copy the padding because it is immutable.
mPadding = state.mPadding;
mLayoutInsets = state.mLayoutInsets;
mDither = state.mDither;
mChangingConfigurations = state.mChangingConfigurations;
mTargetDensity = state.mTargetDensity;
}
@Override
public Drawable newDrawable() {
return new NinePatchDrawable(this, null);
}
@Override
public Drawable newDrawable(Resources res) {
return new NinePatchDrawable(this, res);
}
@Override
public int getChangingConfigurations() {
return mChangingConfigurations;
}
}
private NinePatchDrawable(NinePatchState state, Resources res) {
setNinePatchState(state, res);
}
}