Android - 自定义可手势移动、放缩ImageView
程序员文章站
2024-03-24 14:52:10
...
又很长的一段时间没写博客,趁着空闲之余,做一下一个常用的自定义View的记录。
手势移动、放缩ImageView是一个很常用的控件,之前都是用别人写好的,发现对Matrix类不是很熟悉,估计以后可能会经常用到这个类,所以想深入了解一下Matrix原理和怎么使用!
Matrix
网上太多资料了,比如 Android Matrix矩阵,大家自行查资料,只有理解了Matrix变换矩阵,才能看懂下面的代码。
实现手势移动、放缩ImageView
直接上码:
package com.johan.fh.camera.view;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.ImageView;
/**
* Created by johan on 2019/6/22.
*/
public class GestureImageView extends ImageView {
private static final int MODE_NONE = 0;
private static final int MODE_MOVE = 1;
private static final int MODE_SCALE = 2;
/** 上次变换矩阵 */
private Matrix lastMatrix;
/** 当前变换矩阵 */
private Matrix useMatrix;
/** 矩阵变化后Drawable坐标 */
private RectF rect;
/** 手势模式 单指滑动 双指放缩 */
private int mode;
/** 第一只手指开始坐标 */
private float startX, startY;
/** 第二只手指坐标 */
private float start2X, start2Y;
/** 两指中心坐标 作为放缩的中心点 */
private float centerX, centerY;
/** 两指距离 */
private float startDistance;
private boolean animate;
public GestureImageView(Context context) {
super(context);
}
public GestureImageView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
setScaleType(ScaleType.MATRIX);
lastMatrix = new Matrix();
useMatrix = new Matrix();
rect = new RectF();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (animate) return false;
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN :
actionDown(event);
break;
case MotionEvent.ACTION_POINTER_DOWN :
actionPointerDown(event);
break;
case MotionEvent.ACTION_MOVE :
actionMove(event);
break;
case MotionEvent.ACTION_POINTER_UP :
actionPointerUp(event);
break;
case MotionEvent.ACTION_UP :
actionUp(event);
break;
}
return true;
}
/**
* 单指Down事件
* @param event
*/
private void actionDown(MotionEvent event) {
mode = MODE_MOVE;
startX = event.getX();
startY = event.getY();
lastMatrix.set(getImageMatrix());
}
/**
* 多指Down事件
* @param event
*/
private void actionPointerDown(MotionEvent event) {
mode = MODE_SCALE;
startX = event.getX(0);
startY = event.getY(0);
start2X = event.getX(1);
start2Y = event.getY(1);
centerX = startX + (start2X - startX) / 2;
centerY = startY + (start2Y - startY) / 2;
startDistance = calculateDistance(startX, startY, start2X, start2Y);
lastMatrix.set(getImageMatrix());
}
/**
* Move事件
* @param event
*/
private void actionMove(MotionEvent event) {
if (mode == MODE_MOVE) {
float moveX = event.getX() - startX;
float moveY = event.getY() - startY;
// 由于moveX和moveY是居于点击开始的距离
// 所以当前变换矩阵要重置为Down事件开始的变化矩阵
useMatrix.set(lastMatrix);
// 移动距离
useMatrix.postTranslate(moveX, moveY);
// 计算将要变换矩阵左边
matrixRect(useMatrix);
// 边界检测
if (rect.left > 0 || rect.top > 0 || rect.right < getWidth() || rect.bottom < getHeight()) {
useMatrix.set(getImageMatrix());
return;
}
// Image变换
setImageMatrix(useMatrix);
} else if (mode == MODE_SCALE) {
float currentX = event.getX(0);
float currentY = event.getY(0);
float current2X = event.getX(1);
float current2Y = event.getY(1);
float distance = calculateDistance(currentX, currentY, current2X, current2Y);
// 由于distance是居于点击开始的距离
// 所以当前变换矩阵要重置为Down事件开始的变化矩阵
useMatrix.set(lastMatrix);
// 放缩
useMatrix.postScale(distance / startDistance, distance / startDistance, centerX, centerY);
// 计算当前放缩倍数
float scale = getScale(useMatrix);
// 倍数不能小于1而不能大于2
if (scale < 1 || scale > 2) {
useMatrix.set(getImageMatrix());
return;
}
// Image变换
setImageMatrix(useMatrix);
}
}
/**
* 多指Up事件
* @param event
*/
private void actionPointerUp(MotionEvent event) {
// 放缩之后可能边界越界 需要调整
offset();
mode = MODE_NONE;
}
/**
* 单指Up事件
* @param event
*/
private void actionUp(MotionEvent event) {
mode = MODE_NONE;
}
/**
* 计算两指距离
* @param point1X
* @param point1Y
* @param point2X
* @param point2Y
* @return
*/
private float calculateDistance(float point1X, float point1Y, float point2X, float point2Y) {
return (float) Math.sqrt((point2X - point1X) * (point2X - point1X) + (point2Y - point1Y) * (point2Y - point1Y));
}
/**
* 变换矩阵 获取矩阵变化后的坐标
* 由于Move事件比较频繁 如果在方法内创建对象的话 会产生很多垃圾 虽然不大 能避免就避免吧
* @param matrix
*/
private void matrixRect(Matrix matrix) {
if (getDrawable() == null) return;
int drawableWidth = getDrawable().getIntrinsicWidth();
int drawableHeight = getDrawable().getIntrinsicHeight();
rect.set(0, 0, drawableWidth, drawableHeight);
// 这样可以获取矩阵的坐标信息 有用
matrix.mapRect(rect);
}
/**
* 获取矩阵缩放倍数
* @param matrix
* @return
*/
private float getScale(Matrix matrix) {
// 放缩倍数存放于参数 不用计算便可取出来
float[] values = new float[9];
// getValue取出矩阵所有的值
matrix.getValues(values);
return values[Matrix.MSCALE_X];
}
/**
* 放缩之后 计算调整
*/
private void offset() {
matrixRect(useMatrix);
float offsetLeft = 0, offsetTop = 0;
if (rect.left > 0) {
offsetLeft = -rect.left;
}
if (rect.right < getWidth()) {
offsetLeft = getWidth() - rect.right;
}
if (rect.top > 0) {
offsetTop = -rect.top;
}
if (rect.bottom < getHeight()) {
offsetTop = getHeight() - rect.bottom;
}
if (offsetLeft != 0 || offsetTop != 0) {
offsetAnimation(offsetLeft, offsetTop);
}
}
/**
* 调整动画
* @param offsetLeft
* @param offsetTop
*/
private void offsetAnimation(float offsetLeft, float offsetTop) {
int duration = 400;
final float stepLeft = offsetLeft / duration;
final float stepTop = offsetTop / duration;
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, duration);
valueAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
lastMatrix.set(getImageMatrix());
animate = true;
}
@Override
public void onAnimationEnd(Animator animation) {
animate = false;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 注意:上面onAnimationStart方法可能在这个方法之后才调用 判断一下
if (!animate) return;
// 与Move事件原理一样
useMatrix.set(lastMatrix);
// 计算移动
float time = (float) animation.getAnimatedValue();
float left = stepLeft * time;
float top = stepTop * time;
// 矩阵移动
useMatrix.postTranslate(left, top);
// Image变换
setImageMatrix(useMatrix);
}
});
valueAnimator.setDuration(duration);
valueAnimator.start();
}
@Override
public void setImageDrawable(@Nullable Drawable drawable) {
super.setImageDrawable(drawable);
reset();
}
@Override
public void setImageResource(@DrawableRes int resId) {
super.setImageResource(resId);
reset();
}
/**
* 重新设置Drawable 需要手动重置矩阵
*/
private void reset() {
lastMatrix.reset();
setImageMatrix(lastMatrix);
}
}
这里我们需要注意的是定义了2个Matrix,lastMatrix矩阵主要是记录点击前的ImageMatrix,useMatrix使用之前,都要set回到点击前的变换矩阵,因为move事件计算的数值都是点击开始的,之后才能正常使用!
至于其他的吧,代码已经有详细的注释了,相信聪明的你看得懂!!
最好能自己动手试试,毕竟纸上得来终觉浅!
上一篇: flutter Gesture 手势处理
下一篇: 数据结构与算法:汉诺塔问题(C++)