您的位置:首页 > 其它

结合Matrix实现ZoomImageView,包括双击缩放,多点缩放。

2016-02-12 20:13 393 查看
Matrix是一个包含图像信息的3X3的矩阵,包括图片的以下四个信息,可以通过对矩阵中相应的数值进行操作



Translate 平移变换

Rotate 旋转变换

Scale 缩放变换

Skew 错切变换

在代码中使用一个数组来表示这9个数值。

我写的ZoomImageView主要涉及到了缩放、平移,matrix类中有postTranslate(float dx,float dy);postScale(float dx,float dy)等post方法可以对数组的相应数值进行后乘操作;以及mMatrix.setScale( float sx, float sy, float px, float py)等set方法对矩阵中相应的数值进行直接操作,但是这里有一个问题:

以倍数为例

**setScale( float sx, float sy, float px, float py)方法指定的中心并不精确,而postScale放大的倍数并不精确,**postScale只能在原有基础上放大指定的倍数,如果需要实现动画效果,则需要指定一个与1接近的参数不停的执行后乘方法,直到超过目标倍数,这个结果最后也不精确,所以我想到了属性动画中的数值发生器 ,这样既实现了动画效果,也保证了结果的精确,比较难的就是需要计算最终状态的倍数。最终的位移值也需要计算,从而实现以指定的点为中心放大图片。

以下为代码:(主要就是计算过程以及各种状态的判断比较复杂)

public class ZoomImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,
ScaleGestureDetector.OnScaleGestureListener, OnTouchListener {
/**
* 图片处理
*/
private Matrix mMatrix;
/**
* 多点触控
*/
private ScaleGestureDetector mScaleGestureDetector;
/**
* 按下时X坐标
*/
private float mDownX;
/**
* 按下时Y坐标
*/
private float mDownY;
/**
* matrix数组
*/
private float[] mFloats = new float[9];
/**
* 双击手势控制
*/
private GestureDetector mGestureDetector;
/**
* 存储各个状态下的缩放值
*/
private HashMap<Integer, Float> mScaleMap;
/**
* 记录在最先状态时的坐标,即matrix数组中的2,5项
*/
private HashMap<Integer, Float[]> mMinPoint;
/**
* 是否双击
*/
private boolean isDoubleTap;
/**
* 是否在拖动
*/
private boolean isDraging;
/**
* 是否在自动变化
*/
private boolean isAutoChange;
/**
* 缩放状态
*/
public static final int STATUS_SCALE = 1;
/**
* 移动状态
*/
public static final int STATUS_MOVE = 2;
/**
* 记录当前图片状态
*/
private int USER_OPERTION_STATUS = STATUS_SCALE;
/**
* 默认状态,即图片显示的初始状态
*/
public static final int IMAGE_STATUS_DEFAULT = 0;
/**
* 保证屏幕被完全覆盖
*/
public static final int IMAGE_STATUS_FIT_WINDOW = 1;
/**
* 最大状态,如果放大默认倍数后仍然不能适应窗口,则将显示适应窗口状态
*/
public static final int IMAGE_STATUS_MAX = 2;
/**
* 最小状态,如果初始状态就是缩小后的图片则与默认状态相同,否则scale值为1时为最小状态
*/
public static final int IMAGE_STATUS_MIN = 3;
/**
* 图片当前的状态
*/
private int IMAGE_STATUS = IMAGE_STATUS_DEFAULT;

private boolean mOnce = false;

public ZoomImageView(Context context) {
this(context, null);
}

public ZoomImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public ZoomImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}

public void init() {
setScaleType(ScaleType.MATRIX);
mMatrix = new Matrix();
mScaleGestureDetector = new ScaleGestureDetector(getContext(), this);
setOnTouchListener(this);
mGestureDetector = new GestureDetector(getContext(),
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
isDoubleTap = true;
switch (IMAGE_STATUS) {
case IMAGE_STATUS_DEFAULT:
if (mScaleMap.get(IMAGE_STATUS_DEFAULT).equals(mScaleMap.get(IMAGE_STATUS_FIT_WINDOW))) {
setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MAX));
IMAGE_STATUS = IMAGE_STATUS_MAX;
} else {
setScaleToFitWindow(getScale(), mScaleMap.get(IMAGE_STATUS_FIT_WINDOW), e.getX(), e.getY());
IMAGE_STATUS = IMAGE_STATUS_FIT_WINDOW;
}
break;
case IMAGE_STATUS_FIT_WINDOW:
if (mScaleMap.get(IMAGE_STATUS_FIT_WINDOW).equals(mScaleMap.get(IMAGE_STATUS_MAX))) {
setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MIN));
IMAGE_STATUS = IMAGE_STATUS_MIN;
} else {
setScaleToMax(e.getX(), e.getY());
IMAGE_STATUS = IMAGE_STATUS_MAX;
}
break;
case IMAGE_STATUS_MAX:
if (mScaleMap.get(IMAGE_STATUS_MIN).equals(mScaleMap.get(IMAGE_STATUS_DEFAULT))) {
setScale(getScale(), mScaleMap.get(IMAGE_STATUS_DEFAULT));
IMAGE_STATUS = IMAGE_STATUS_DEFAULT;
} else {
setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MIN));
IMAGE_STATUS = IMAGE_STATUS_MIN;
}
break;
case IMAGE_STATUS_MIN:
setScale(getScale(), mScaleMap.get(IMAGE_STATUS_DEFAULT));
IMAGE_STATUS = IMAGE_STATUS_DEFAULT;
break;
}
if (mScaleMap.get(IMAGE_STATUS) * getDrawable().getMinimumHeight() <= getHeight()
&& mScaleMap.get(IMAGE_STATUS) * getDrawable().getMinimumWidth() <= getWidth()) {
moveToMiddle();
}
isDoubleTap = false;
return true;
}
});
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
if (getDrawable() == null) {
return;
}
if (!mOnce) {
Drawable drawable = getDrawable();
if (drawable == null) {
return;
}
int drawableW = drawable.getMinimumWidth();
int drawableH = drawable.getMinimumHeight();
float viewH = getHeight();
float viewW = getWidth();
float scale = Math.min(viewW / drawableW, viewH / drawableH);
mMatrix.postTranslate(viewW / 2 - drawableW / 2, viewH / 2 - drawableH / 2);
mMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);
setImageMatrix(mMatrix);
putScaleMapAndPoint();
mOnce = true;
}
}

/**
* 当控件挂载到窗口时为其添加OnGlobalLayoutListener接口,并将图片设置到默认状态
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}

@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}

/**
* 存储各个状态下的缩放值和最小值的Matrix.MTRANS_X和Matrix.MTRANS_Y值
*/
private void putScaleMapAndPoint() {
mScaleMap = new HashMap<>();
mScaleMap.put(IMAGE_STATUS_DEFAULT, getScale());
mScaleMap.put(IMAGE_STATUS_MIN, Math.min(1, getScale()));
if (getDrawableHeight() == getHeight() && getDrawableWidth() == getWidth()) {
mScaleMap.put(IMAGE_STATUS_FIT_WINDOW, getScale());
} else if (getDrawableHeight() == getHeight()) {
mScaleMap.put(IMAGE_STATUS_FIT_WINDOW, getScale() * getWidth() / getDrawableWidth());
} else if (getDrawableWidth() == getWidth()) {
mScaleMap.put(IMAGE_STATUS_FIT_WINDOW, getScale() * getHeight() / getDrawableHeight());
}
if (mScaleMap.get(IMAGE_STATUS_FIT_WINDOW) >= 3) {
mScaleMap.put(IMAGE_STATUS_MAX, mScaleMap.get(IMAGE_STATUS_FIT_WINDOW));
} else {
mScaleMap.put(IMAGE_STATUS_MAX, 3.0f);
}
mMatrix.getValues(mFloats);
mMinPoint = new HashMap<>();
float x = ((float) getWidth() - (float) getDrawable().getMinimumWidth() * mScaleMap.get(IMAGE_STATUS_MIN)) / 2;
float y = ((float) getHeight() - (float) getDrawable().getMinimumHeight() * mScaleMap.get(IMAGE_STATUS_MIN)) / 2;
mMinPoint.put(IMAGE_STATUS_MIN, new Float[]{x, y});
}

@Override
public boolean onTouch(View v, MotionEvent event) {
if (getDrawable() == null) {
return true;
}
if (isAutoChange) {
return true;
}
if (mGestureDetector.onTouchEvent(event)) {
return true;
}
isDoubleTap = false;
if (event.getPointerCount() == 2) {
USER_OPERTION_STATUS = STATUS_SCALE;
}
mScaleGestureDetector.onTouchEvent(event);
mMatrix.getValues(mFloats);
if (USER_OPERTION_STATUS == STATUS_SCALE && event.getPointerCount() == 1 &&
event.getAction() == MotionEvent.ACTION_UP) {
USER_OPERTION_STATUS = STATUS_MOVE;
}
if (USER_OPERTION_STATUS == STATUS_SCALE) {
return true;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = event.getRawX();
mDownY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (getScale() <= mScaleMap.get(IMAGE_STATUS_DEFAULT)) {
break;
}
if (Math.abs(event.getRawX() - mDownX) > 18 || Math.abs(event.getRawY() - mDownY) > 18 || isDraging) {
isDraging = true;
mFloats[Matrix.MTRANS_X] = event.getRawX() - mDownX + mFloats[Matrix.MTRANS_X];
mFloats[Matrix.MTRANS_Y] = event.getRawY() - mDownY + mFloats[Matrix.MTRANS_Y];
mDownY = event.getRawY();
mDownX = event.getRawX();
mMatrix.setValues(mFloats);
setImageMatrix(mMatrix);
}
break;
case MotionEvent.ACTION_UP:
if (getScale() <= mScaleMap.get(IMAGE_STATUS_DEFAULT)) {
break;
}
if (isDraging) {
isDraging = false;
checkBorder();
}
break;
}
return true;
}

@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
return true;
}

@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
if (getDrawable() == null) {
return true;
}
setImageMatrix(mMatrix);
return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector detector) {
if (getScale() < mScaleMap.get(IMAGE_STATUS_DEFAULT)) {
IMAGE_STATUS = IMAGE_STATUS_MIN;
}
if (getScale() > mScaleMap.get(IMAGE_STATUS_DEFAULT) && getScale() < mScaleMap.get(IMAGE_STATUS_FIT_WINDOW)) {
IMAGE_STATUS = IMAGE_STATUS_DEFAULT;
}
if (getScale() > mScaleMap.get(IMAGE_STATUS_MAX)) {
IMAGE_STATUS = IMAGE_STATUS_MAX;
setScaleToMax(detector.getPreviousSpanX(), detector.getPreviousSpanY());
}
if (getScale() < mScaleMap.get(IMAGE_STATUS_MIN)) {
IMAGE_STATUS = IMAGE_STATUS_MIN;
setScale(getScale(), mScaleMap.get(IMAGE_STATUS_MIN));
}
checkBorder();
}

/**
* 移动到到屏幕合适位置,保证没有空白
*/
private void moveToFitView() {
if (!isDoubleTap) {
mMatrix.getValues(mFloats);
if (mFloats[Matrix.MTRANS_X] > 0) {
setPost(mFloats[Matrix.MTRANS_X], 0, Matrix.MTRANS_X);
}
if (mFloats[Matrix.MTRANS_X] + getDrawableWidth() < getWidth()) {
setPost(mFloats[Matrix.MTRANS_X] + getDrawableWidth(), getWidth(), Matrix.MTRANS_X, getDrawableWidth());
}
if (mFloats[Matrix.MTRANS_Y] > 0) {
setPost(mFloats[Matrix.MTRANS_Y], 0, Matrix.MTRANS_Y);
}
if (mFloats[Matrix.MTRANS_Y] + getDrawableHeight() < getHeight()) {
setPost(mFloats[Matrix.MTRANS_Y] + getDrawableHeight(), getHeight(), Matrix.MTRANS_Y, getDrawableHeight());
}
}
}

/**
* 移动图片到垂直方向的中间位置
*/
private void moveToVerticalMiddle() {
if (!isDoubleTap) {
mMatrix.getValues(mFloats);
setPost(mFloats[Matrix.MTRANS_Y], getHeight() / 2 - getDrawableHeight() / 2, Matrix.MTRANS_Y);
if (mFloats[Matrix.MTRANS_X] > 0) {
setPost(mFloats[Matrix.MTRANS_X], 0, Matrix.MTRANS_X);
}
if (mFloats[Matrix.MTRANS_X] + getDrawableWidth() < getWidth()) {
setPost(mFloats[Matrix.MTRANS_X] + getDrawableWidth(), getWidth(), Matrix.MTRANS_X, getDrawableWidth());
}
}
}

/**
* 移动图片到水平位置的中间位置
*/
private void moveToHorizontalMiddle() {
mMatrix.getValues(mF
f601
loats);
if (!isDoubleTap) {
setPost(mFloats[Matrix.MTRANS_X], getWidth() / 2 - getDrawableWidth() / 2, Matrix.MTRANS_X);
if (mFloats[Matrix.MTRANS_Y] > 0) {
setPost(mFloats[Matrix.MTRANS_Y], 0, Matrix.MTRANS_Y);
}
if (mFloats[Matrix.MTRANS_Y] + getDrawableHeight() < getHeight()) {
setPost(mFloats[Matrix.MTRANS_Y] + getDrawableHeight(), getHeight(), Matrix.MTRANS_Y, getDrawableHeight());
}
}
}

/**
* 移动图片到屏幕的中间
*/
private void moveToMiddle() {
mMatrix.getValues(mFloats);
if (isDoubleTap || getScale() < mScaleMap.get(IMAGE_STATUS_MIN)) {
if (IMAGE_STATUS == IMAGE_STATUS_MIN ||
((mScaleMap.get(IMAGE_STATUS_MIN).equals(mScaleMap.get(IMAGE_STATUS_DEFAULT)) &&
IMAGE_STATUS == IMAGE_STATUS_DEFAULT))) {
setPost(mFloats[Matrix.MTRANS_Y], mMinPoint.get(IMAGE_STATUS_MIN)[1], Matrix.MTRANS_Y);
setPost(mFloats[Matrix.MTRANS_X], mMinPoint.get(IMAGE_STATUS_MIN)[0], Matrix.MTRANS_X);
} else {
setPost(mFloats[Matrix.MTRANS_X], mMinPoint.get(IMAGE_STATUS_MIN)[0] -
((getDrawable().getMinimumWidth() * mScaleMap.get(IMAGE_STATUS_MIN)) *
(mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1)) / 2, Matrix.MTRANS_X);

setPost(mFloats[Matrix.MTRANS_Y], mMinPoint.get(IMAGE_STATUS_MIN)[1] -
((getDrawable().getMinimumHeight() * mScaleMap.get(IMAGE_STATUS_MIN)) *
(mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1)) / 2, Matrix.MTRANS_Y);
}
} else {
setPost(mFloats[Matrix.MTRANS_Y], getHeight() / 2 - getDrawableHeight() / 2, Matrix.MTRANS_Y);
setPost(mFloats[Matrix.MTRANS_X], getWidth() / 2 - getDrawableWidth() / 2, Matrix.MTRANS_X);
}
}

/**
* 数值生成器来控制图片变化
*
* @param startValue 初始值,也就是图片缩放后的值
* @param endValue   最终值
* @param matrix     matrix数组需要改变的数组位置
*/
private void setPost(float startValue, float endValue, final int matrix) {
setPost(startValue, endValue, matrix, 0);
}

/**
* 数值生成器来控制图片变化
*
* @param startValue 初始值,也就是图片缩放后的值
* @param endValue   最终值
* @param matrix     matrix数组需要改变的数组位置
* @param x          对生成的数值的修正值
*/
private void setPost(final float startValue, final float endValue, final int matrix, final float x) {
ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue);
yValueAnimator.setDuration(200).start();
yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mMatrix.getValues(mFloats);
mFloats[matrix] = (Float) animation.getAnimatedValue() - x;
isAutoChange = !((Float) animation.getAnimatedValue() == endValue);
mMatrix.setValues(mFloats);
setImageMatrix(mMatrix);
}
});
yValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
isAutoChange = false;
}

@Override
public void onAnimationEnd(Animator animation) {
isAutoChange = false;
}
});
}

/**
* 数值生成器来控制图片变化
*
* @param startValue 初始值,也就是图片缩放后的值
* @param endValue   最终值
*/
private void setScale(final float startValue, final float endValue) {
ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue);
yValueAnimator.setDuration(200).start();
yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mMatrix.getValues(mFloats);
mFloats[Matrix.MSCALE_X] = (float) animation.getAnimatedValue();
mFloats[Matrix.MSCALE_Y] = (float) animation.getAnimatedValue();
isAutoChange = !((float) animation.getAnimatedValue() == endValue);
mMatrix.setValues(mFloats);
setImageMatrix(mMatrix);
}
});

yValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
isAutoChange = false;
}

@Override
public void onAnimationEnd(Animator animation) {
isAutoChange = false;
}
});
}

/**
* 缩放到保证覆盖屏幕
*
* @param startValue 初始值,目前图片缩放的值
* @param endValue   缩放后的值
* @param x          点击事件的X坐标
* @param y          点击事件的Y坐标
*/
private void setScaleToFitWindow(final float startValue, final float endValue, final float x, final float y) {
mMatrix.getValues(mFloats);
final float tranX = mFloats[2];
final float tranY = mFloats[5];
final float drawableH = mScaleMap.get(IMAGE_STATUS_DEFAULT) * getDrawable().getMinimumHeight();
final float drawableW = mScaleMap.get(IMAGE_STATUS_DEFAULT) * getDrawable().getMinimumWidth();
final ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue);
yValueAnimator.setDuration(200).start();
yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mMatrix.getValues(mFloats);
mFloats[Matrix.MSCALE_X] = (float) animation.getAnimatedValue();
mFloats[Matrix.MSCALE_Y] = (float) animation.getAnimatedValue();

float a = ((float) animation.getAnimatedValue() - startValue) / (endValue - startValue);
if (drawableH == getHeight()) {
mFloats[2] = tranX - tranX * a;
float end = -(mScaleMap.get(IMAGE_STATUS_FIT_WINDOW) / mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1) * y;
mFloats[5] = (end - tranY) * a + tranY;
} else if (drawableW == getWidth()) {
mFloats[5] = tranY - tranY * a;
float end = -(mScaleMap.get(IMAGE_STATUS_FIT_WINDOW) / mScaleMap.get(IMAGE_STATUS_DEFAULT) - 1) * x;
mFloats[2] = (end - tranX) * a + tranX;
}
isAutoChange = !((float) animation.getAnimatedValue() == endValue);
mMatrix.setValues(mFloats);
setImageMatrix(mMatrix);
}
});

yValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
isAutoChange = false;
}

@Override
public void onAnimationEnd(Animator animation) {
isAutoChange = false;
}
});
}

/**
* 缩放到MAX值
*
* @param x 点击事件的X坐标
* @param y 点击事件的Y坐标
*/
private void setScaleToMax(final float x, final float y) {
final float startValue = getScale();
final float endValue = mScaleMap.get(IMAGE_STATUS_MAX);
mMatrix.getValues(mFloats);
final float tranX = mFloats[2];
final float tranY = mFloats[5];
final float scale = mScaleMap.get(IMAGE_STATUS_MAX) / getScale();
final ValueAnimator yValueAnimator = ValueAnimator.ofFloat(startValue, endValue);
yValueAnimator.setDuration(200).start();
yValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mMatrix.getValues(mFloats);
mFloats[Matrix.MSCALE_X] = (float) animation.getAnimatedValue();
mFloats[Matrix.MSCALE_Y] = (float) animation.getAnimatedValue();
Log.e(mFloats[2] + "", mFloats[5] + "");
float a = ((float) animation.getAnimatedValue() - startValue) / (endValue - startValue);
float endX = (x - (x - tranX) * scale);
float endY = (y - (y - tranY) * scale);
mFloats[2] = (endX - tranX) * a + tranX;
mFloats[5] = (endY - tranY) * a + tranY;
isAutoChange = !((float) animation.getAnimatedValue() == endValue);
mMatrix.setValues(mFloats);
setImageMatrix(mMatrix);
}
});

yValueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationCancel(Animator animation) {
isAutoChange = false;
}

@Override
public void onAnimationEnd(Animator animation) {
isAutoChange = false;
checkBorder();
}
});
}

/**
* 检查边界是否可以覆盖保证不留有空白
*/
private void checkBorder() {
if (getDrawableHeight() <= getHeight() && getDrawableWidth() <= getWidth()) {
moveToMiddle();
} else if (getDrawableHeight() > getHeight() && getDrawableWidth() <= getWidth()) {
moveToHorizontalMiddle();
} else if (getDrawableWidth() > getWidth() && getDrawableHeight() <= getHeight()) {
moveToVerticalMiddle();
} else if (getDrawableHeight() > getHeight() && getDrawableWidth() > getWidth()) {
moveToFitView();
}
}

/**
* @return 获取当前缩放值
*/
private float getScale() {
float[] floats = new float[9];
mMatrix.getValues(floats);
return floats[Matrix.MSCALE_X];
}

/**
* @return 缩放后图片的宽度
*/
private float getDrawableWidth() {
return getScale() * getDrawable().getMinimumWidth();
}

/**
* @return 缩放后图片的高度
*/
private float getDrawableHeight() {
return getScale() * getDrawable().getMinimumHeight();
}

@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
onAttachedToWindow();
init();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  matrix 图片