package com.glview.graphics.drawable;
import java.io.IOException;
import java.io.InputStream;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.BitmapFactory.Options;
import android.graphics.PixelFormat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import com.glview.R;
import com.glview.graphics.Bitmap;
import com.glview.graphics.BitmapFactory;
import com.glview.graphics.Rect;
import com.glview.graphics.drawable.ninepatch.NinePatch;
import com.glview.hwui.GLCanvas;
import com.glview.hwui.GLPaint;
public class NinePatchDrawable extends Drawable {
// dithering helps a lot, and is pretty cheap, so default is true
private static final boolean DEFAULT_DITHER = false;
private NinePatchState mNinePatchState;
private NinePatch mNinePatch;
private Rect mPadding;
private Insets mOpticalInsets = Insets.NONE;
private GLPaint mPaint = new GLPaint();
private boolean mMutated;
private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
// These are scaled to match the target density.
private int mBitmapWidth = -1;
private int mBitmapHeight = -1;
NinePatchDrawable() {
mNinePatchState = new NinePatchState();
}
/**
* 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, 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;
}
/**
* The one constructor to rule them all. This is called by all public
* constructors to set the state and initialize local properties.
*/
private NinePatchDrawable(NinePatchState state, Resources res) {
mNinePatchState = state;
initializeWithState(state, res);
}
/**
* Initializes local dynamic properties from state.
*/
private void initializeWithState(NinePatchState state, Resources res) {
if (res != null) {
mTargetDensity = res.getDisplayMetrics().densityDpi;
} else {
mTargetDensity = state.mTargetDensity;
}
// Make a local copy of the padding.
if (state.mPadding != null) {
mPadding = new Rect(state.mPadding);
}
setNinePatch(state.mNinePatch);
}
/**
* 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 = scaleFromDensity(insets.left, sdensity, tdensity);
int top = scaleFromDensity(insets.top, sdensity, tdensity);
int right = scaleFromDensity(insets.right, sdensity, tdensity);
int bottom = 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();
mOpticalInsets = mNinePatchState.mOpticalInsets;
} else {
mBitmapWidth = scaleFromDensity(mNinePatch.getWidth(), sdensity, tdensity);
mBitmapHeight = 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 = scaleFromDensity(src.left, sdensity, tdensity);
dest.top = scaleFromDensity(src.top, sdensity, tdensity);
dest.right = scaleFromDensity(src.right, sdensity, tdensity);
dest.bottom = scaleFromDensity(src.bottom, sdensity, tdensity);
}
mOpticalInsets = scaleFromDensity(mNinePatchState.mOpticalInsets, sdensity, tdensity);
}
}
/**
* @hide
*/
static public int scaleFromDensity(int size, int sdensity, int tdensity) {
if (sdensity == Bitmap.DENSITY_NONE || tdensity == Bitmap.DENSITY_NONE || sdensity == tdensity) {
return size;
}
// Scale by tdensity / sdensity, rounding up.
return ((size * tdensity) + (sdensity >> 1)) / sdensity;
}
private void setNinePatch(NinePatch ninePatch) {
if (mNinePatch != ninePatch) {
mNinePatch = ninePatch;
if (ninePatch != null) {
computeBitmapSize();
} else {
mBitmapWidth = mBitmapHeight = -1;
mOpticalInsets = Insets.NONE;
}
invalidateSelf();
}
}
@Override
public void draw(GLCanvas canvas) {
final Rect bounds = getBounds();
final int restoreAlpha;
if (mNinePatchState.mBaseAlpha != 1.0f) {
restoreAlpha = mPaint.getAlpha();
mPaint.setAlpha((int) (restoreAlpha * mNinePatchState.mBaseAlpha + 0.5f));
} else {
restoreAlpha = -1;
}
mNinePatch.draw(canvas, bounds, mPaint);
if (restoreAlpha >= 0) {
mPaint.setAlpha(restoreAlpha);
}
}
@Override
public int getChangingConfigurations() {
return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations;
}
@Override
public boolean getPadding(Rect padding) {
final Rect scaledPadding = mPadding;
if (scaledPadding != null) {
if (needsMirroring()) {
padding.set(scaledPadding.right, scaledPadding.top,
scaledPadding.left, scaledPadding.bottom);
} else {
padding.set(scaledPadding);
}
return (padding.left | padding.top | padding.right | padding.bottom) != 0;
}
return false;
}
/**
* @hide
*/
@Override
public Insets getOpticalInsets() {
if (needsMirroring()) {
return Insets.of(mOpticalInsets.right, mOpticalInsets.top,
mOpticalInsets.left, mOpticalInsets.bottom);
} else {
return mOpticalInsets;
}
}
@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 int getAlpha() {
if (mPaint == null) {
// Fast common case -- normal alpha.
return 0xFF;
}
return getPaint().getAlpha();
}
@Override
public void setDither(boolean dither) {
//noinspection PointlessBooleanExpression
if (mPaint == null && dither == DEFAULT_DITHER) {
// Fast common case -- leave at default dither.
return;
}
// getPaint().setDither(dither);
invalidateSelf();
}
@Override
public void setAutoMirrored(boolean mirrored) {
mNinePatchState.mAutoMirrored = mirrored;
}
private boolean needsMirroring() {
return false;//isAutoMirrored() && getLayoutDirection() == LayoutDirection.RTL;
}
@Override
public boolean isAutoMirrored() {
return mNinePatchState.mAutoMirrored;
}
@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);
final TypedArray a = obtainAttributes(r, attrs, R.styleable.NinePatchDrawable);
updateStateFromTypedArray(a);
a.recycle();
}
/**
* Updates the constant state from the values in the typed array.
*/
private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
final Resources r = a.getResources();
final NinePatchState state = mNinePatchState;
final int srcResId = a.getResourceId(R.styleable.NinePatchDrawable_src, 0);
if (srcResId != 0) {
final Options options = new Options();
final Rect padding = new Rect();
final Rect opticalInsets = new Rect();
Bitmap bitmap = null;
try {
final TypedValue value = new TypedValue();
final InputStream is = r.openRawResource(srcResId, value);
bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options);
is.close();
} catch (IOException e) {
// Ignore
}
if (bitmap == null) {
throw new XmlPullParserException(a.getPositionDescription() +
": <nine-patch> requires a valid src attribute");
} else if (bitmap.getNinePatchChunk() == null) {
throw new XmlPullParserException(a.getPositionDescription() +
": <nine-patch> requires a valid 9-patch source image");
}
state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk());
state.mPadding = padding;
}
// Update local properties.
initializeWithState(state, r);
// Push density applied by setNinePatchState into state.
state.mTargetDensity = mTargetDensity;
}
public GLPaint getPaint() {
if (mPaint == null) {
mPaint = new GLPaint();
}
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 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;
}
@Override
protected boolean onStateChange(int[] stateSet) {
final NinePatchState state = mNinePatchState;
if (state.mTint != null) {
return true;
}
return false;
}
@Override
public boolean isStateful() {
final NinePatchState s = mNinePatchState;
return super.isStateful() || (s.mTint != null && s.mTint.isStateful());
}
final static class NinePatchState extends ConstantState {
// Values loaded during inflation.
NinePatch mNinePatch = null;
ColorStateList mTint = null;
Rect mPadding = null;
Insets mOpticalInsets = Insets.NONE;
float mBaseAlpha = 1.0f;
boolean mDither = DEFAULT_DITHER;
int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT;
boolean mAutoMirrored = false;
int mChangingConfigurations;
NinePatchState() {
// Empty constructor.
}
NinePatchState(NinePatch ninePatch, Rect padding) {
this(ninePatch, padding, null, DEFAULT_DITHER, false);
}
NinePatchState(NinePatch ninePatch, Rect padding,
Rect opticalInsets) {
this(ninePatch, padding, opticalInsets, DEFAULT_DITHER, false);
}
NinePatchState(NinePatch ninePatch, Rect padding,
Rect opticalInsets, boolean dither, boolean autoMirror) {
mNinePatch = ninePatch;
mPadding = padding;
mOpticalInsets = Insets.of(opticalInsets);
mDither = dither;
mAutoMirrored = autoMirror;
}
// Copy constructor
NinePatchState(NinePatchState state) {
// We don't deep-copy any fields because they are all immutable.
mNinePatch = state.mNinePatch;
mTint = state.mTint;
mPadding = state.mPadding;
mBaseAlpha = state.mBaseAlpha;
mDither = state.mDither;
mChangingConfigurations = state.mChangingConfigurations;
mTargetDensity = state.mTargetDensity;
mAutoMirrored = state.mAutoMirrored;
}
@Override
public Bitmap getBitmap() {
return mNinePatch.getBitmap();
}
@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;
}
}
}