Android手势ImageView三部曲 第一部
发布时间 - 2026-01-10 23:28:09 点击率:次这几天一直在研究github上的PhotoView跟GestureImageView,发现写的都很牛,看了很久的代码,于是打算把自己所看的一些东西总结一下,内容还是很多的,但是很有含金量哈~~

先附上两个开源项目的链接:
GestureImageView: https://github.com/jasonpolites/gesture-imageview
PhotoView:https://github.com/chrisbanes/PhotoView
这样说有点乏味哈,先看看我们今天要实现的效果:
当一个手指按住图片的时候,此时的效果为拖拽的效果。
当两个手指按住图片的时候,手指旋转则图片跟着旋转,手指缩放则图片缩放。
效果图大致为(我模拟器不太好模拟旋转):
好了下面我们来实现一下这个手势缩放ImageView:
首先我们创建一个叫MatrixImageView的类去继承ImageView,然后重写其构造方法(我就不考虑直接new的情况了哈):
public class MatrixImageView2 extends ImageView {
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
}
然后我们需要定义几种当前view的状态:
MODE_NONE(初始状态);
MODE_DRAG(拖拽状态);
MODE_ZOOM(两个手指缩放状态)
public class MatrixImageView2 extends ImageView {
private static final int MODE_NONE = 190;
private static final int MODE_DRAG = 468;
private static final int MODE_ZOOM = 685;
.....
}
我们对ImageView做旋转、缩放、位移等操作主要是用到ImageView的这个方法:
/**
* Adds a transformation {@link Matrix} that is applied
* to the view's drawable when it is drawn. Allows custom scaling,
* translation, and perspective distortion.
*
* @param matrix the transformation parameters in matrix form
*/
public void setImageMatrix(Matrix matrix) {
// collapse 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();
}
}
利用的是Matrix这个类(对这个类不懂的童鞋自己去查资料哈~),然后通过监听我们的onTouchEvent方法获取当前手势操作,然后对matrix进行相应操作,改变图片的状态。
代码比较短,而且我每行都注释了,我就直接给代码了:
MatrixImageView.java:
package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.widget.ImageView;
public class MatrixImageView2 extends ImageView {
private static final int MODE_NONE = 190;
private static final int MODE_DRAG = 468;
private static final int MODE_ZOOM = 685;
//当前mode
private int mode;
//手指按下时候的坐标
private float startX, startY;
//两个手指中间点的位置
private float midX, midY;
//当前imageview的matirx对象,以前imageview的matrix对象
private Matrix currMatrix, savedMatrix;
//之前图片的旋转角度
private float preRotate;
//之间两个手指之间的距离
private float preSpacing;
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
//初始化模式为初始状态
mode = MODE_NONE;
currMatrix = new Matrix();
savedMatrix = new Matrix();
DisplayMetrics dm = getResources().getDisplayMetrics();
//给ImageView设置一张图片(此处为了测试直接在imageview里面设置了一张测试图片)
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//多点触碰如果需要监听ACTION_POINTER_DOWN等操作的时候,必须用event.getAction() & MotionEvent.ACTION_MASK
//而不是直接的event.getAction();
switch (event.getAction() & MotionEvent.ACTION_MASK) {
//当一个手指按下的时候
case MotionEvent.ACTION_DOWN:
//保存当前imageview的matrix对象
savedMatrix.set(currMatrix);
//记录手指开始的坐标
startX = event.getX();
startY = event.getY();
//此时的状态为拖拽状态
mode = MODE_DRAG;
break;
//当两个手指按下的时候(我们先不考虑很多个的情况哈,能力有限~!)
case MotionEvent.ACTION_POINTER_DOWN:
//计算两个手指之间的距离并保存起来
preSpacing = calSpacing(event);
//如果两个手指之间的距离大于我们指定的一个值后(改变状态为缩放)
if (preSpacing > 10f) {
savedMatrix.set(currMatrix);
mode = MODE_ZOOM;
//记录下缩放的中间坐标值
midX = (event.getX(0) + event.getX(1)) / 2;
midY = (event.getY(0) + event.getY(1)) / 2;
}
//根据两个手指的位置计算出当前角度并保存
preRotate = calRotate(event);
break;
//当手指移动的时候
case MotionEvent.ACTION_MOVE:
//如果之前给的状态为拖拽状态的时候
if (mode == MODE_DRAG) {
//首先把之前的matrix的状态赋给当前的matrix对象
currMatrix.set(savedMatrix);
//算出手指移动的距离
float dx = event.getX() - startX;
float dy = event.getY() - startY;
//把手指移动的距离设置给matrix对象
currMatrix.postTranslate(dx, dy);
//当状态为放大状态的时候,并且有两个手指按下的时候
} else if (mode == MODE_ZOOM && event.getPointerCount() == 2) {
//首先把之前的matrix的状态赋给当前的matrix对象
currMatrix.set(savedMatrix);
//计算出此时两个手指之间的距离
float spacing = calSpacing(event);
//如果此时两手指之间的距离大于我们给定的值
if (spacing > 10f) {
//此时两手指距离/第二个手指刚按下时两手指的距离
float scale = spacing / preSpacing;
//把算出的缩放值给当前matrix对象,(缩放中心点为之前算出的mid)
currMatrix.postScale(scale, scale, midX, midY);
}
//根据两手指位置算出此时的旋转角度
float rotate = calRotate(event);
if (rotate != preRotate) {
//算出此时需要旋转的角度
rotate = rotate - preRotate;
//开始旋转图片
currMatrix.postRotate(rotate, getMeasuredWidth() / 2, getMeasuredHeight() / 2);
}
}
break;
}
//最后记得把当前的matrix对象给imageview
setImageMatrix(currMatrix);
return true;
}
/**
* 根据两手指的位置算出角度
* 勾股定理 tan0=x(两手指横坐标距离)/y(两手指纵坐标距离);
* @param event
* @return
*/
private float calRotate(MotionEvent event) {
double x = event.getX(0) - event.getX(1);
double y = event.getY(0) - event.getY(1);
double radius = Math.atan2(y, x);
return (float) Math.toDegrees(radius);
}
/**
* 两个点距离公式为d*d=(x1-x0)的平方+(y1-y0)的平方
* @param event
* @return
*/
private float calSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
}
}
然后添加我们的布局文件:
<com.leo.gestureimageview.MatrixImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix" android:src="@mipmap/test" />
最后运行代码:
好了,虽说我们是实现了我们的手势imageview的基本功能,但是如果要处理那种多点(>两个手指)触碰,还有一些复杂的操作的时候,我们的onTouchEvent里面写的代码可能就不止这么一点了(还是有点复杂的,考虑的因素太多),但如果可以把某个事件的处理单独拿出去分成很多个分支的话,还会这么复杂么?? 如果说我们的代码可以像下面这样的话,你是不是觉得很爽呢?
package com.leo.gestureimageview;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.ImageView;
import com.leo.gestureimageview.GestureDetectors.MoveGestureDetector;
import com.leo.gestureimageview.GestureDetectors.RotateGestureDetector;
public class MatrixImageView2 extends ImageView {
private Matrix mMatrix = new Matrix();
private float mScaleFactor =1f;
private float mRotationDegrees = 0.f;
private float mFocusX = 0.f;
private float mFocusY = 0.f;
private ScaleGestureDetector mScaleDetector;
private RotateGestureDetector mRotateDetector;
private MoveGestureDetector mMoveDetector;
public MatrixImageView2(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
private void initView() {
//初始化模式为初始状态
DisplayMetrics dm = getResources().getDisplayMetrics();
//给ImageView设置一张图片(此处为了测试直接在imageview里面设置了一张测试图片)
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.test);
bitmap = Bitmap.createScaledBitmap(bitmap, dm.widthPixels, dm.heightPixels, true);
setImageBitmap(bitmap);
mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
mRotateDetector = new RotateGestureDetector(getContext(), new RotateListener());
mMoveDetector = new MoveGestureDetector(getContext(), new MoveListener());
mFocusX = dm.widthPixels/2f;
mFocusY = dm.heightPixels/2f;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mScaleDetector.onTouchEvent(event);
mRotateDetector.onTouchEvent(event);
mMoveDetector.onTouchEvent(event);
return true;
}
private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
@Override
public boolean onScale(ScaleGestureDetector detector) {
mScaleFactor *= detector.getScaleFactor(); // scale change since previous event
// Don't let the object get too small or too large.
mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 10.0f));
changeMatrix();
return true;
}
}
private class RotateListener extends RotateGestureDetector.SimpleOnRotateGestureListener {
@Override
public boolean onRotate(RotateGestureDetector detector) {
mRotationDegrees -= detector.getRotationDegreesDelta();
changeMatrix();
return true;
}
}
private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
@Override
public boolean onMove(MoveGestureDetector detector) {
PointF d = detector.getFocusDelta();
mFocusX += d.x;
mFocusY += d.y;
changeMatrix();
return true;
}
}
private void changeMatrix(){
float scaledImageCenterX = (getDrawable().getIntrinsicWidth()*mScaleFactor)/2;
float scaledImageCenterY = (getDrawable().getIntrinsicHeight()*mScaleFactor)/2;
mMatrix.reset();
mMatrix.postScale(mScaleFactor, mScaleFactor);
mMatrix.postRotate(mRotationDegrees, scaledImageCenterX, scaledImageCenterY);
mMatrix.postTranslate(mFocusX - scaledImageCenterX, mFocusY - scaledImageCenterY);
setImageMatrix(mMatrix);
}
}
我们的ImageView的onTouchEvent就只剩下短短的几行代码了,然后各个detector处理完事件后,我们只需要拿到处理好的值就可以了:
@Override
public boolean onTouchEvent(MotionEvent event) {
//把缩放事件给mScaleDetector
mScaleDetector.onTouchEvent(event);
//把旋转事件个mRotateDetector
mRotateDetector.onTouchEvent(event);
//把移动事件给mMoveDetector
mMoveDetector.onTouchEvent(event);
return true;
}
是不是觉得很爽呢? 是的,我也是无意中看到了这个开源项目,先附上这个框架的github链接:
https://github.com/Almeros/android-gesture-detectors
下一节我们将深入了解detector,以及系统自带的手势处理工具类GestureDetector,感兴趣的小伙伴请继续关注哦。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Android
# 手势
# ImageView
# Android手势ImageView三部曲 第二部
# Android自定义GestureDetector实现手势ImageView
# Android使用ImageView实现支持手势缩放效果
# Android ImageView随手势变化动态缩放图片
# Android手势滑动实现ImageView缩放图片大小
# Android实现手势控制ImageView图片大小
# Android通过手势实现的缩放处理实例代码
# android开发之为activity增加左右手势识别示例
# android使用gesturedetector手势识别示例分享
# Android手势ImageView三部曲 第三部
# 按下
# 拖拽
# 多点
# 好了
# 开源
# 计算出
# 触碰
# 很爽
# 的是
# 并保存
# 勾股定理
# 我就
# 看了
# 太多
# 你是
# 就不
# 还会
# 很有
# 不懂
# 很久
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel如何实现API版本控制_Laravel API版本化路由设计策略
进行网站优化必须要坚持的四大原则
如何快速搭建高效香港服务器网站?
Windows Hello人脸识别突然无法使用
Laravel如何实现数据库事务?(DB Facade示例)
IOS倒计时设置UIButton标题title的抖动问题
Laravel的Blade指令怎么自定义_创建你自己的Laravel Blade Directives
Laravel如何配置和使用缓存?(Redis代码示例)
Laravel如何操作JSON类型的数据库字段?(Eloquent示例)
如何在Windows虚拟主机上快速搭建网站?
Laravel如何发送系统通知?(Notification渠道示例)
Laravel如何生成和使用数据填充?(Seeder和Factory示例)
Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】
什么是javascript作用域_全局和局部作用域有什么区别?
php后缀怎么变mp4格式错误_修改扩展名提示格式不对怎么办【技巧】
JS去除重复并统计数量的实现方法
rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
无锡营销型网站制作公司,无锡网选车牌流程?
如何快速生成ASP一键建站模板并优化安全性?
uc浏览器二维码扫描入口_uc浏览器扫码功能使用地址
Laravel如何配置Horizon来管理队列?(安装和使用)
Python文件操作最佳实践_稳定性说明【指导】
PythonWeb开发入门教程_Flask快速构建Web应用
HTML 中如何正确使用模板变量为元素的 name 属性赋值
北京专业网站制作设计师招聘,北京白云观官方网站?
iOS中将个别页面强制横屏其他页面竖屏
佛山企业网站制作公司有哪些,沟通100网上服务官网?
实例解析angularjs的filter过滤器
JS中页面与页面之间超链接跳转中文乱码问题的解决办法
Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解
Chrome浏览器标签页分组怎么用_谷歌浏览器整理标签页技巧【效率】
node.js报错:Cannot find module 'ejs'的解决办法
夸克浏览器网页跳转延迟怎么办 夸克浏览器跳转优化
Android仿QQ列表左滑删除操作
Laravel Debugbar怎么安装_Laravel调试工具栏配置指南
Laravel怎么配置.env环境变量_Laravel生产环境敏感数据保护与读取【方法】
Laravel如何生成PDF或Excel文件_Laravel文档导出工具与使用教程
高防服务器租用指南:配置选择与快速部署攻略
网站制作公司哪里好做,成都网站制作公司哪家做得比较好,更正规?
Laravel软删除怎么实现_Laravel Eloquent SoftDeletes功能使用教程
Laravel如何使用Eloquent进行子查询
胶州企业网站制作公司,青岛石头网络科技有限公司怎么样?
谷歌浏览器下载文件时中断怎么办 Google Chrome下载管理修复
网站制作价目表怎么做,珍爱网婚介费用多少?
Laravel如何使用Eloquent ORM进行数据库操作?(CRUD示例)
北京的网站制作公司有哪些,哪个视频网站最好?
如何用VPS主机快速搭建个人网站?
JavaScript模板引擎Template.js使用详解
浅述节点的创建及常见功能的实现

