Files
frameworks_base/graphics/java/android/graphics/drawable/NinePatchDrawable.java
Dianne Hackborn b8a9f12802 Fix #2101821: Cut labels of menu items in "SnapTell" market app.
Ummmm...  this turns out to be pretty bad.  NinePatchDrawable was not
scaling its reported padding for compatibility mode, causing spacing
to be off.  All over the place.  This change should improve things quite
a bit (and magically makes nearly all of the menu flaws go away).

Change-Id: I94a8310d95b908b6f087db97d9afaed654ca6de5
2009-09-23 11:27:06 -07:00

388 lines
12 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.graphics.*;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
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, described in <a link="../../../resources.html#ninepatch">
* Resources</a>.
*
*/
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 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 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, null), 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, null), 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;
if (DEFAULT_DITHER != state.mDither) {
// 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) {
mTargetDensity = metrics.densityDpi;
if (mNinePatch != 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 (mNinePatch != null) {
computeBitmapSize();
}
}
private void computeBitmapSize() {
final int sdensity = mNinePatch.getDensity();
final int tdensity = mTargetDensity;
if (sdensity == tdensity) {
mBitmapWidth = mNinePatch.getWidth();
mBitmapHeight = mNinePatch.getHeight();
} else {
mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(),
sdensity, tdensity);
mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(),
sdensity, tdensity);
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);
}
}
// overrides
@Override
public void draw(Canvas canvas) {
if (false) {
float[] pts = new float[2];
canvas.getMatrix().mapPoints(pts);
Log.v("9patch", "Drawing 9-patch @ " + pts[0] + "," + pts[1] + ": " + getBounds());
}
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;
}
@Override
public void setAlpha(int alpha) {
getPaint().setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter cf) {
getPaint().setColorFilter(cf);
}
@Override
public void setDither(boolean dither) {
getPaint().setDither(dither);
}
@Override
public void setFilterBitmap(boolean filter) {
// at the moment, we see no quality improvement, but a big slowdown
// with filtering, so ignore this call for now
//
//getPaint().setFilterBitmap(filter);
}
@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 = DisplayMetrics.DENSITY_DEVICE;
final Rect padding = 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, 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 = super.getChangingConfigurations();
return mNinePatchState;
}
@Override
public Drawable mutate() {
if (!mMutated && super.mutate() == this) {
mNinePatchState = new NinePatchState(mNinePatchState);
mNinePatch = mNinePatchState.mNinePatch;
mMutated = true;
}
return this;
}
final static class NinePatchState extends ConstantState {
final NinePatch mNinePatch;
final Rect mPadding;
final boolean mDither;
int mChangingConfigurations;
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
NinePatchState(NinePatch ninePatch, Rect padding) {
this(ninePatch, padding, DEFAULT_DITHER);
}
NinePatchState(NinePatch ninePatch, Rect rect, boolean dither) {
mNinePatch = ninePatch;
mPadding = rect;
mDither = dither;
}
NinePatchState(NinePatchState state) {
mNinePatch = new NinePatch(state.mNinePatch);
// Note we don't copy the padding because it is immutable.
mPadding = state.mPadding;
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);
}
}