/* * 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. * *
*

Developer Guides

*

For more information about how to use a NinePatchDrawable, read the * * Canvas and Drawables developer guide. For information about creating a NinePatch image * file using the draw9patch tool, see the * Draw 9-patch tool guide.

*/ 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 static 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() + ": 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() + ": requires a valid src attribute"); } else if (bitmap.getNinePatchChunk() == null) { throw new XmlPullParserException(parser.getPositionDescription() + ": 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); } }