package com.glview.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import com.glview.R;
import com.glview.content.GLContext;
import com.glview.graphics.Bitmap;
import com.glview.graphics.drawable.BitmapDrawable;
import com.glview.graphics.drawable.Drawable;
import com.glview.hwui.GLCanvas;
import com.glview.view.View;
public class ImageView extends View {
private final static String TAG = "ImageView";
protected RectF mImageDrawRect; //临时方案,最好通过控制matrix来进行操作
private int mResource = 0;
Drawable mDrawable;
int mImageResource;
boolean mSetImageBitmap = true;
boolean mSetPreloadTexture;
private Matrix mMatrix;
private int[] mState = null;
private boolean mMergeState = false;
private int mLevel = 0;
private int mDrawableWidth;
private int mDrawableHeight;
private Matrix mDrawMatrix = null;
// Avoid allocations...
private RectF mTempSrc = new RectF();
private RectF mTempDst = new RectF();
private ScaleType mScaleType = ScaleType.HORIZONTAL_CENTER;
private boolean mAdjustViewBounds = false;
private int mMaxWidth = Integer.MAX_VALUE;
private int mMaxHeight = Integer.MAX_VALUE;
private boolean mHaveFrame = false;
public final static int CREATE_IMAGE = 2;
// AdjustViewBounds behavior will be in compatibility mode for older apps.
private boolean mAdjustViewBoundsCompat = false;
private static final ScaleType[] sScaleTypeArray = {
ScaleType.MATRIX,
ScaleType.FIT_XY,
ScaleType.FIT_START,
ScaleType.FIT_CENTER,
ScaleType.FIT_END,
ScaleType.CENTER,
ScaleType.CENTER_CROP,
ScaleType.CENTER_INSIDE,
ScaleType.HORIZONTAL_CENTER
};
public ImageView(Context context) {
super(context);
initImageView();
}
public ImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public ImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initImageView();
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ImageView, defStyleAttr, defStyleRes);
Drawable d = GLContext.get().getResources().getDrawable(a.getResourceId(R.styleable.ImageView_src, 0));//a.getDrawable(R.styleable.ImageView_src);
if (d != null) {
setImageDrawable(d);
}
setAdjustViewBounds(a.getBoolean(R.styleable.ImageView_adjustViewBounds, false));
setMaxWidth(a.getDimensionPixelSize(
R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
setMaxHeight(a.getDimensionPixelSize(
R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
int index = a.getInt(R.styleable.ImageView_scaleType, -1);
if (index >= 0) {
setScaleType(sScaleTypeArray[index]);
}
a.recycle();
//need inflate syntax/reader for matrix
}
private void initImageView() {
mMatrix = new Matrix();
mScaleType = ScaleType.HORIZONTAL_CENTER;
mAdjustViewBoundsCompat = mContext.getApplicationInfo().targetSdkVersion <=
Build.VERSION_CODES.JELLY_BEAN_MR1;
mImageDrawRect = new RectF();
}
/**
* True when ImageView is adjusting its bounds
* to preserve the aspect ratio of its drawable
*
* @return whether to adjust the bounds of this view
* to presrve the original aspect ratio of the drawable
*
* @see #setAdjustViewBounds(boolean)
*
* @attr ref android.R.styleable#ImageView_adjustViewBounds
*/
public boolean getAdjustViewBounds() {
return mAdjustViewBounds;
}
/**
* Set this to true if you want the ImageView to adjust its bounds
* to preserve the aspect ratio of its drawable.
*
* <p><strong>Note:</strong> If the application targets API level 17 or lower,
* adjustViewBounds will allow the drawable to shrink the view bounds, but not grow
* to fill available measured space in all cases. This is for compatibility with
* legacy {@link android.view.View.MeasureSpec MeasureSpec} and
* {@link android.widget.RelativeLayout RelativeLayout} behavior.</p>
*
* @param adjustViewBounds Whether to adjust the bounds of this view
* to preserve the original aspect ratio of the drawable.
*
* @see #getAdjustViewBounds()
*
* @attr ref android.R.styleable#ImageView_adjustViewBounds
*/
public void setAdjustViewBounds(boolean adjustViewBounds) {
mAdjustViewBounds = adjustViewBounds;
if (adjustViewBounds) {
setScaleType(ScaleType.FIT_CENTER);
}
}
/**
* The maximum width of this view.
*
* @return The maximum width of this view
*
* @see #setMaxWidth(int)
*
* @attr ref android.R.styleable#ImageView_maxWidth
*/
public int getMaxWidth() {
return mMaxWidth;
}
/**
* An optional argument to supply a maximum width for this view. Only valid if
* {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum
* of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
* adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
* layout params to WRAP_CONTENT.
*
* <p>
* Note that this view could be still smaller than 100 x 100 using this approach if the original
* image is small. To set an image to a fixed size, specify that size in the layout params and
* then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
* the image within the bounds.
* </p>
*
* @param maxWidth maximum width for this view
*
* @see #getMaxWidth()
*
*/
public void setMaxWidth(int maxWidth) {
mMaxWidth = maxWidth;
}
/**
* The maximum height of this view.
*
* @return The maximum height of this view
*
* @see #setMaxHeight(int)
*
*/
public int getMaxHeight() {
return mMaxHeight;
}
/**
* An optional argument to supply a maximum height for this view. Only valid if
* {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a
* maximum of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
* adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
* layout params to WRAP_CONTENT.
*
* <p>
* Note that this view could be still smaller than 100 x 100 using this approach if the original
* image is small. To set an image to a fixed size, specify that size in the layout params and
* then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
* the image within the bounds.
* </p>
*
* @param maxHeight maximum height for this view
*
* @see #getMaxHeight()
*
*/
public void setMaxHeight(int maxHeight) {
mMaxHeight = maxHeight;
}
private static final Matrix.ScaleToFit[] sS2FArray = {
Matrix.ScaleToFit.FILL,
Matrix.ScaleToFit.START,
Matrix.ScaleToFit.CENTER,
Matrix.ScaleToFit.END
};
private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st) {
// ScaleToFit enum to their corresponding Matrix.ScaleToFit values
return sS2FArray[st.nativeInt - 1];
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
resolveUri();
int w;
int h;
// Desired aspect ratio of the view's contents (not including padding)
float desiredAspect = 0.0f;
// We are allowed to change the view's width
boolean resizeWidth = false;
// We are allowed to change the view's height
boolean resizeHeight = false;
final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
if (mDrawable == null) {
// If no texture, its intrinsic size is 0.
mDrawableWidth = -1;
mDrawableHeight = -1;
w = h = 0;
} else {
w = mDrawableWidth;
h = mDrawableHeight;
if (w <= 0) w = 1;
if (h <= 0) h = 1;
// We are supposed to adjust view bounds to match the aspect
// ratio of our drawable. See if that is possible.
if (mAdjustViewBounds) {
resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
desiredAspect = (float) w / (float) h;
}
}
int pleft = mPaddingLeft;
int pright = mPaddingRight;
int ptop = mPaddingTop;
int pbottom = mPaddingBottom;
int widthSize;
int heightSize;
if (resizeWidth || resizeHeight) {
/* If we get here, it means we want to resize to match the
drawables aspect ratio, and we have the freedom to change at
least one dimension.
*/
// Get the max possible width given our constraints
widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
// Get the max possible height given our constraints
heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
if (desiredAspect != 0.0f) {
// See what our actual aspect ratio is
float actualAspect = (float)(widthSize - pleft - pright) /
(heightSize - ptop - pbottom);
if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
boolean done = false;
// Try adjusting width to be proportional to height
if (resizeWidth) {
int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
pleft + pright;
// Allow the width to outgrow its original estimate if height is fixed.
if (!resizeHeight && !mAdjustViewBoundsCompat) {
widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
}
if (newWidth <= widthSize) {
widthSize = newWidth;
done = true;
}
}
// Try adjusting height to be proportional to width
if (!done && resizeHeight) {
int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
ptop + pbottom;
// Allow the height to outgrow its original estimate if width is fixed.
if (!resizeWidth && !mAdjustViewBoundsCompat) {
heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
heightMeasureSpec);
}
if (newHeight <= heightSize) {
heightSize = newHeight;
}
}
}
}
} else {
/* We are either don't want to preserve the drawables aspect ratio,
or we are not allowed to change view dimensions. Just measure in
the normal way.
*/
w += pleft + pright;
h += ptop + pbottom;
w = Math.max(w, getSuggestedMinimumWidth());
h = Math.max(h, getSuggestedMinimumHeight());
widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
}
setMeasuredDimension(widthSize, heightSize);
}
private int resolveAdjustedSize(int desiredSize, int maxSize,
int measureSpec) {
int result = desiredSize;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
/*
* Parent says we can be as big as we want. Just don't be larger
* than max size imposed on ourselves.
*/
result = Math.min(desiredSize, maxSize);
break;
case MeasureSpec.AT_MOST:
// Parent says we can be as big as we want, up to specSize.
// Don't be larger than specSize, and don't be larger than
// the max size imposed on ourselves.
result = Math.min(Math.min(desiredSize, specSize), maxSize);
break;
case MeasureSpec.EXACTLY:
// No choice. Do what we are told.
result = specSize;
break;
}
return result;
}
@Override
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = super.setFrame(left, top, right, bottom);
mHaveFrame = true;
configureBounds();
return changed;
}
//TODO:继续完善下面的代码,目前以mImageDrawRect作为显示区域,后面可以采用更合理的方案
private void configureBounds() {
if (mDrawable == null || !mHaveFrame) {
return;
}
int dwidth = mDrawableWidth;
int dheight = mDrawableHeight;
int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
int vheight = getHeight() - mPaddingTop - mPaddingBottom;
boolean fits = (dwidth < 0 || vwidth == dwidth) &&
(dheight < 0 || vheight == dheight);
if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
/* If the drawable has no intrinsic size, or we're told to
scaletofit, then we just fill our entire view.
*/
// mDrawable.setBounds(0, 0, vwidth, vheight);
mDrawMatrix = null;
mImageDrawRect.set(0, 0, vwidth, vheight);
} else {
// We need to do the scaling ourself, so have the drawable
// use its native size.
// mDrawable.setBounds(0, 0, dwidth, dheight);
mImageDrawRect.set(0, 0, dwidth, dheight);
if (ScaleType.MATRIX == mScaleType) {
// Use the specified matrix as-is.
if (mMatrix.isIdentity()) {
mDrawMatrix = null;
} else {
mDrawMatrix = mMatrix;
}
} else if (fits) {
// The bitmap fits exactly, no transform needed.
mDrawMatrix = null;
} else if(ScaleType.HORIZONTAL_CENTER == mScaleType){
mDrawMatrix = null;
mImageDrawRect.set((int) ((vwidth - dwidth) * 0.5f + 0.5f), 0,
(int) ((vwidth + dwidth) * 0.5f + 0.5f), dheight);
} else if (ScaleType.CENTER == mScaleType) {
// Center bitmap in view, no scaling.
mDrawMatrix = mMatrix;
mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
(int) ((vheight - dheight) * 0.5f + 0.5f));
} else if (ScaleType.CENTER_CROP == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx = 0, dy = 0;
if (dwidth * vheight > vwidth * dheight) {
scale = (float) vheight / (float) dheight;
dx = (vwidth - dwidth * scale) * 0.5f;
} else {
scale = (float) vwidth / (float) dwidth;
dy = (vheight - dheight * scale) * 0.5f;
}
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
} else if (ScaleType.CENTER_INSIDE == mScaleType) {
mDrawMatrix = mMatrix;
float scale;
float dx;
float dy;
if (dwidth <= vwidth && dheight <= vheight) {
scale = 1.0f;
} else {
scale = Math.min((float) vwidth / (float) dwidth,
(float) vheight / (float) dheight);
}
dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
mDrawMatrix.setScale(scale, scale);
mDrawMatrix.postTranslate(dx, dy);
} else {
// Generate the required transform.
mTempSrc.set(0, 0, dwidth, dheight);
mTempDst.set(0, 0, vwidth, vheight);
mDrawMatrix = mMatrix;
mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
}
}
if (mDrawMatrix != null) {
mDrawMatrix.mapRect(mImageDrawRect);
}
mDrawable.setBounds((int) mImageDrawRect.left, (int) mImageDrawRect.top, (int) mImageDrawRect.right, (int) mImageDrawRect.bottom);
}
/**
* Sets a drawable as the content of this ImageView.
*
* <p class="note">This does Bitmap reading and decoding on the UI
* thread, which can cause a latency hiccup. If that's a concern,
* consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
* {@link #setImageBitmap(android.graphics.Bitmap)} and
* {@link android.graphics.BitmapFactory} instead.</p>
*
* @param resId the resource identifier of the the drawable
*
*/
public void setImageResource(int resId) {
if (mResource != resId) {
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(null);
mResource = resId;
resolveUri();
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
private void resolveUri() {
if (mDrawable != null) {
return;
}
Drawable d = null;
if (mResource != 0) {
try {
d = getResources().getDrawable(mResource);
} catch (Exception e) {
Log.w("ImageView", "Unable to find resource: " + mResource, e);
}
}
updateDrawable(d);
}
/**
* Sets a Bitmap as the content of this ImageView.
*
* @param bm The bitmap to set
*/
public void setImageBitmap(Bitmap bm) {
// if this is used frequently, may handle bitmaps explicitly
// to reduce the intermediate drawable object
setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
}
public void setImageState(int[] state, boolean merge) {
mState = state;
mMergeState = merge;
if (mDrawable != null) {
refreshDrawableState();
resizeFromDrawable();
}
}
@Override
public void setSelected(boolean selected) {
super.setSelected(selected);
resizeFromDrawable();
}
/**
* Sets the image level, when it is constructed from a
* {@link android.graphics.drawable.LevelListDrawable}.
*
* @param level The new level for the image.
*/
public void setImageLevel(int level) {
mLevel = level;
if (mDrawable != null) {
mDrawable.setLevel(level);
resizeFromDrawable();
}
}
/**
* Sets a drawable as the content of this ImageView.
*
* @param drawable The drawable to set
*/
public void setImageDrawable(Drawable drawable) {
if (mDrawable != drawable) {
mResource = 0;
final int oldWidth = mDrawableWidth;
final int oldHeight = mDrawableHeight;
updateDrawable(drawable);
if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
requestLayout();
}
invalidate();
}
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
if (mState == null) {
return super.onCreateDrawableState(extraSpace);
} else if (!mMergeState) {
return mState;
} else {
return mergeDrawableStates(
super.onCreateDrawableState(extraSpace + mState.length), mState);
}
}
private void updateDrawable(Drawable d) {
if (mDrawable != null) {
mDrawable.setCallback(null);
unscheduleDrawable(mDrawable);
}
mDrawable = d;
if (d != null) {
d.setCallback(this);
d.setLayoutDirection(getLayoutDirection());
if (d.isStateful()) {
d.setState(getDrawableState());
}
d.setVisible(getVisibility() == VISIBLE, true);
d.setLevel(mLevel);
mDrawableWidth = d.getIntrinsicWidth();
mDrawableHeight = d.getIntrinsicHeight();
// applyImageTint();
// applyColorMod();
configureBounds();
} else {
mDrawableWidth = mDrawableHeight = -1;
}
}
private void resizeFromDrawable() {
Drawable d = mDrawable;
if (d != null) {
int w = d.getIntrinsicWidth();
if (w < 0) w = mDrawableWidth;
int h = d.getIntrinsicHeight();
if (h < 0) h = mDrawableHeight;
if (w != mDrawableWidth || h != mDrawableHeight) {
mDrawableWidth = w;
mDrawableHeight = h;
requestLayout();
}
}
}
@Override
protected void onDraw(GLCanvas canvas) {
super.onDraw(canvas);
if (mDrawable == null) {
return; // couldn't resolve the URI
}
if (mDrawableWidth == 0 || mDrawableHeight == 0) {
return; // nothing to draw (empty bounds)
}
if (mPaddingTop == 0 && mPaddingLeft == 0) {
mDrawable.draw(canvas);
} else {
canvas.translate(mPaddingLeft, mPaddingTop);
mDrawable.draw(canvas);
canvas.translate(- mPaddingLeft, - mPaddingTop);
}
}
/**
* Options for scaling the bounds of an image to the bounds of this view.
*/
public enum ScaleType {
/**
* Scale using the image matrix when drawing. The image matrix can be set using
* {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
* <code>android:scaleType="matrix"</code>.
*/
MATRIX (0),
/**
* Scale the image using {@link Matrix.ScaleToFit#FILL}.
* From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
*/
FIT_XY (1),
/**
* Scale the image using {@link Matrix.ScaleToFit#START}.
* From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
*/
FIT_START (2),
/**
* Scale the image using {@link Matrix.ScaleToFit#CENTER}.
* From XML, use this syntax:
* <code>android:scaleType="fitCenter"</code>.
*/
FIT_CENTER (3),
/**
* Scale the image using {@link Matrix.ScaleToFit#END}.
* From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
*/
FIT_END (4),
/**
* Center the image in the view, but perform no scaling.
* From XML, use this syntax: <code>android:scaleType="center"</code>.
*/
CENTER (5),
/**
* Scale the image uniformly (maintain the image's aspect ratio) so
* that both dimensions (width and height) of the image will be equal
* to or larger than the corresponding dimension of the view
* (minus padding). The image is then centered in the view.
* From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
*/
CENTER_CROP (6),
/**
* Scale the image uniformly (maintain the image's aspect ratio) so
* that both dimensions (width and height) of the image will be equal
* to or less than the corresponding dimension of the view
* (minus padding). The image is then centered in the view.
* From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
*/
CENTER_INSIDE (7),
HORIZONTAL_CENTER (8);
ScaleType(int ni) {
nativeInt = ni;
}
final int nativeInt;
}
/**
* Controls how the image should be resized or moved to match the size
* of this ImageView.
*
* @param scaleType The desired scaling mode.
*
*/
public void setScaleType(ScaleType scaleType) {
if (scaleType == null) {
throw new NullPointerException();
}
if (mScaleType != scaleType) {
mScaleType = scaleType;
}
}
public ScaleType getScaleType(){
return mScaleType;
}
public void setImageMatrix(Matrix matrix) {
// collaps null and identity to just null
if (matrix != null && matrix.isIdentity()) {
matrix = null;
}
// don't invalidate unless we're actually changing our matrix
if (matrix == null && !mMatrix.isIdentity() ||
matrix != null && !mMatrix.equals(matrix)) {
mMatrix.set(matrix);
configureBounds();
invalidate();
}
}
}