复制代码
package org.yan.app.components;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import android.view.ViewTreeObserver;
import androidx.appcompat.widget.AppCompatImageView;
/**
* 支持双指缩放、单指平移、双击缩放的ImageView
* 用于文件预览对话框中的图片预览
*/
public class ZoomableImageView extends AppCompatImageView implements
ScaleGestureDetector.OnScaleGestureListener,
GestureDetector.OnGestureListener,
GestureDetector.OnDoubleTapListener,
View.OnTouchListener {
// 缩放范围限制
private static final float MIN_SCALE = 0.5f;
private static final float MAX_SCALE = 5.0f;
private static final float MID_SCALE = 2.0f;
// 手势检测器
private ScaleGestureDetector mScaleDetector;
private GestureDetector mGestureDetector;
// 变换矩阵
private Matrix mMatrix = new Matrix();
private float[] mMatrixValues = new float[9];
// 触摸状态
private int mMode = MODE_NONE;
private static final int MODE_NONE = 0;
private static final int MODE_DRAG = 1;
private static final int MODE_ZOOM = 2;
// 拖动相关
private PointF mLastTouch = new PointF();
private PointF mStartTouch = new PointF();
// 初始缩放比例(适应屏幕)
private float mInitScale = 1f;
private boolean mIsInitialized = false;
public ZoomableImageView(Context context) {
super(context);
init(context);
}
public ZoomableImageView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
public ZoomableImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mScaleDetector = new ScaleGestureDetector(context, this);
mGestureDetector = new GestureDetector(context, this);
mGestureDetector.setOnDoubleTapListener(this);
setScaleType(ScaleType.MATRIX);
setOnTouchListener(this);
// 监听布局完成后初始化矩阵
getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (!mIsInitialized) {
initMatrix();
mIsInitialized = true;
}
}
});
}
/**
* 初始化矩阵,使图片居中并适应屏幕
*/
private void initMatrix() {
Drawable drawable = getDrawable();
if (drawable == null) return;
int viewWidth = getWidth();
int viewHeight = getHeight();
int drawableWidth = drawable.getIntrinsicWidth();
int drawableHeight = drawable.getIntrinsicHeight();
if (viewWidth == 0 || viewHeight == 0 || drawableWidth == 0 || drawableHeight == 0) {
return;
}
// 计算适应屏幕的缩放比例
float scaleX = (float) viewWidth / drawableWidth;
float scaleY = (float) viewHeight / drawableHeight;
mInitScale = Math.min(scaleX, scaleY);
// 计算居中偏移
float dx = (viewWidth - drawableWidth * mInitScale) / 2f;
float dy = (viewHeight - drawableHeight * mInitScale) / 2f;
mMatrix.reset();
mMatrix.postScale(mInitScale, mInitScale);
mMatrix.postTranslate(dx, dy);
setImageMatrix(mMatrix);
}
@Override
public void setImageDrawable(Drawable drawable) {
super.setImageDrawable(drawable);
mIsInitialized = false;
if (getWidth() > 0 && getHeight() > 0) {
initMatrix();
mIsInitialized = true;
}
}
/**
* 获取当前缩放比例
*/
private float getCurrentScale() {
mMatrix.getValues(mMatrixValues);
return mMatrixValues[Matrix.MSCALE_X];
}
/**
* 获取图片当前显示的矩形区域
*/
private RectF getImageRect() {
Drawable drawable = getDrawable();
if (drawable == null) return new RectF();
RectF rect = new RectF(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
mMatrix.mapRect(rect);
return rect;
}
// ==================== OnTouchListener ====================
@Override
public boolean onTouch(View v, MotionEvent event) {
// 先让手势检测器处理
mScaleDetector.onTouchEvent(event);
mGestureDetector.onTouchEvent(event);
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mLastTouch.set(event.getX(), event.getY());
mStartTouch.set(event.getX(), event.getY());
mMode = MODE_DRAG;
break;
case MotionEvent.ACTION_POINTER_DOWN:
mMode = MODE_ZOOM;
break;
case MotionEvent.ACTION_MOVE:
if (mMode == MODE_DRAG && !mScaleDetector.isInProgress()) {
float dx = event.getX() - mLastTouch.x;
float dy = event.getY() - mLastTouch.y;
// 只有缩放后才允许平移
float currentScale = getCurrentScale();
if (currentScale > mInitScale) {
// 检查边界
RectF rect = getImageRect();
float[] delta = checkBounds(dx, dy, rect);
mMatrix.postTranslate(delta[0], delta[1]);
setImageMatrix(mMatrix);
}
mLastTouch.set(event.getX(), event.getY());
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
mMode = MODE_NONE;
break;
}
return true;
}
/**
* 检查平移边界,防止图片移出可视区域
*/
private float[] checkBounds(float dx, float dy, RectF rect) {
int viewWidth = getWidth();
int viewHeight = getHeight();
// 水平方向边界检查
if (rect.width() <= viewWidth) {
dx = 0;
} else {
if (rect.left + dx > 0) {
dx = -rect.left;
}
if (rect.right + dx < viewWidth) {
dx = viewWidth - rect.right;
}
}
// 垂直方向边界检查
if (rect.height() <= viewHeight) {
dy = 0;
} else {
if (rect.top + dy > 0) {
dy = -rect.top;
}
if (rect.bottom + dy < viewHeight) {
dy = viewHeight - rect.bottom;
}
}
return new float[]{dx, dy};
}
// ==================== ScaleGestureDetector.OnScaleGestureListener ====================
@Override
public boolean onScale(ScaleGestureDetector detector) {
float scaleFactor = detector.getScaleFactor();
float currentScale = getCurrentScale();
float targetScale = currentScale * scaleFactor;
// 限制缩放范围
if (targetScale < MIN_SCALE) {
scaleFactor = MIN_SCALE / currentScale;
} else if (targetScale > MAX_SCALE) {
scaleFactor = MAX_SCALE / currentScale;
}
// 以手指中心点为缩放中心
mMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY());
// 检查并修正边界
checkAndFixBounds();
setImageMatrix(mMatrix);
return true;
}
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
mMode = MODE_ZOOM;
return true;
}
@Override
public void onScaleEnd(ScaleGestureDetector detector) {
// 如果缩放比例小于初始比例,恢复到初始状态
float currentScale = getCurrentScale();
if (currentScale < mInitScale) {
resetToInitState();
}
}
/**
* 检查并修正边界
*/
private void checkAndFixBounds() {
RectF rect = getImageRect();
int viewWidth = getWidth();
int viewHeight = getHeight();
float dx = 0, dy = 0;
// 水平方向
if (rect.width() <= viewWidth) {
dx = (viewWidth - rect.width()) / 2f - rect.left;
} else {
if (rect.left > 0) {
dx = -rect.left;
}
if (rect.right < viewWidth) {
dx = viewWidth - rect.right;
}
}
// 垂直方向
if (rect.height() <= viewHeight) {
dy = (viewHeight - rect.height()) / 2f - rect.top;
} else {
if (rect.top > 0) {
dy = -rect.top;
}
if (rect.bottom < viewHeight) {
dy = viewHeight - rect.bottom;
}
}
mMatrix.postTranslate(dx, dy);
}
/**
* 恢复到初始状态
*/
private void resetToInitState() {
mMatrix.reset();
initMatrix();
}
// ==================== GestureDetector.OnDoubleTapListener ====================
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
float currentScale = getCurrentScale();
float targetScale;
// 双击切换缩放状态
if (currentScale < MID_SCALE) {
targetScale = MID_SCALE;
} else {
targetScale = mInitScale;
}
// 以点击位置为中心缩放
float scaleFactor = targetScale / currentScale;
mMatrix.postScale(scaleFactor, scaleFactor, e.getX(), e.getY());
checkAndFixBounds();
setImageMatrix(mMatrix);
return true;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
// ==================== GestureDetector.OnGestureListener ====================
@Override
public boolean onDown(MotionEvent e) {
return true;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
/**
* 重置图片到初始状态
*/
public void reset() {
mIsInitialized = false;
initMatrix();
mIsInitialized = true;
}
}