Android多点触控之ZoomImageView完全解析
ZoomImageView是一个类似photoview的图片预览控件,实现了对图片的手势放大缩小平移,以及双击放大缩小解决和viewpager滑动冲突等功能,主要是通过GestureDetector,Matrix相关api以及对事件的ontouch处理实现的,这个代码网上能搜索到很多,但是因为注释少所以要完全读懂有些困难,于是本人整理了一段时间,写了一个完整的注释版本,基本每行代码都做到了讲解,大家共同学习。
public class ZoomImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,ScaleGestureDetector.OnScaleGestureListener,View.OnTouchListener{@SuppressWarnings("unused")private static final String TAG = "ZoomImageView";/*** 最大放大倍数*/public static final float mMaxScale = 4.0f;/*** 默认缩放*/private float mInitScale = 1.0f;/*** 双击放大比例*/private float mMidScale=2.0f;/*** 检测缩放手势 多点触控手势识别 独立的类不是GestureDetector的子类*/ScaleGestureDetector mScaleGestureDetector = null;//检测缩放的手势/***检测类似长按啊 轻按啊 拖动 快速滑动 双击啊等等 OnTouch方法虽然也可以* 但是对于一些复杂的手势需求自己去通过轨迹时间等等判断很复杂,因此我们采用系统* 提供的手势类进行处理*/private GestureDetector mGestureDetector;/*** 如果正在缩放中就不向下执行,防止多次双击*/private boolean mIsAutoScaling;/*** Matrix的对图像的处理* Translate 平移变换* Rotate 旋转变换* Scale 缩放变换* Skew 错切变换*/Matrix mScaleMatrix = new Matrix();/*** 处理矩阵的9个值*/float[] mMartixValue = new float[9];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);setScaleType(ScaleType.MATRIX);mScaleGestureDetector = new ScaleGestureDetector(context, this);this.setOnTouchListener(this); //缩放的捕获要建立在setOnTouchListener上//符合滑动的距离 它获得的是触发移动事件的最短距离,如果小于这个距离就不触发移动控件,//如viewpager就是用这个距离来判断用户是否翻页mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();//监听双击事件 SimpleOnGestureListener是OnGestureListener接口实现类,//使用这个复写需要的方法就可以不用复写所有的方法mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onDoubleTap(MotionEvent e) {//如果正在缩放中就不向下执行,防止多次双击if (mIsAutoScaling) {return true;}//缩放的中心点float x = e.getX();float y = e.getY();//如果当前缩放值小于这个临界值 则进行放大if (getScale() < mMidScale) {mIsAutoScaling = true;//view中的方法 已x,y为坐标点放大到mMidScale 延时10mspostDelayed(new AutoScaleRunble(mMidScale, x, y), 16);} else {//如果当前缩放值大于这个临界值 则进行缩小操作 缩小到mInitScalemIsAutoScaling = true;postDelayed(new AutoScaleRunble(mInitScale, x, y), 16);}return true;}});}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();getViewTreeObserver().addOnGlobalLayoutListener(this);}//suppress deprecate warning because i have dealt with it@Override@SuppressWarnings("deprecation")protected void onDetachedFromWindow() {super.onDetachedFromWindow();if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {getViewTreeObserver().removeOnGlobalLayoutListener(this);}getViewTreeObserver().removeGlobalOnLayoutListener(this);}//--------------------------implement OnTouchListener----------------------------//***处理现图片放大后移动查看*/private int mLastPointCount;//触摸点发生移动时的触摸点个数private boolean isCanDrag;//判断是否可以拖拽private float mLatX;//记录移动之前按下去的那个坐标点private float mLastY;private int mTouchSlop;//系统默认触发移动事件的最短距离private boolean isCheckTopAndBottom;//是否可以上下拖动private boolean isCheckLeftAndRight;//是否可以左右拖动@Overridepublic boolean onTouch(View v, MotionEvent event) {//双击事件进行关联if (mGestureDetector.onTouchEvent(event)) {//如果是双击的话就直接不向下执行了return true;}//将事件传递给ScaleGestureDetectormScaleGestureDetector.onTouchEvent(event);float x = 0;float y = 0;//可能出现多手指触摸的情况 ACTION_DOWN事件只能执行一次所以多点触控不能在down事件里面处理int pointerCount = event.getPointerCount();for (int i = 0; i < pointerCount; i++) {x += event.getX(i);y += event.getY(i);}//取平均值,得到的就是多点触控后产生的那个点的坐标x /= pointerCount;y /= pointerCount;//每当触摸点发生移动时(从静止到移动),重置mLasX , mLastY mLastPointCount防止再次进入if (mLastPointCount != pointerCount) {//这里加一个参数并且设置成false的目的是,要判断位移的距离是否符合触发移动事件的最短距离isCanDrag = false;//记录移动之前按下去的那个坐标点,记录的值类似于断点续移,下次移动的时候从这个点开始mLatX = x;mLastY = y;}//重新赋值 说明如果是一些列连续滑动的操作就不会再次进入上面的判断 否则会重新确定坐标移动原点mLastPointCount = pointerCount;RectF rectF = getMatrixRectF();switch (event.getAction()) {case MotionEvent.ACTION_DOWN://按下的时候如果发现图片缩放宽或者高大于屏幕宽高则请求viewpager不拦截事件交给ZoomImageView处理//ZoomImageView可以进行缩放操作if (rectF.width() > getWidth() || rectF.height() > getHeight()){getParent().requestDisallowInterceptTouchEvent(true);}break;case MotionEvent.ACTION_MOVE://按下的时候如果发现图片缩放宽或者高大于屏幕宽高则请求viewpager不拦截事件交给ZoomImageView处理//ZoomImageView可以进行缩放操作if (rectF.width() > getWidth() || rectF.height() > getHeight()){getParent().requestDisallowInterceptTouchEvent(true);}//x,y移动的距离float dx = x - mLatX;float dy = y - mLastY;//如果是不能拖拽,可能是因为手指变化,这时就去重新检测看看是不是符合滑动if (!isCanDrag) {//反正是根据勾股定理,调用系统APIisCanDrag = isMoveAction(dx, dy);Log.e(TAG, "移动3---->" + pointerCount);}if (isCanDrag) {if (getDrawable() != null) {//判断是宽或者高小于屏幕,就不在那个方向进行拖拽isCheckLeftAndRight = isCheckTopAndBottom = true;if (rectF.width() < getWidth()) {//如果图片宽度小于控件宽度isCheckLeftAndRight = false;dx = 0;}if (rectF.height() < getHeight()) { //如果图片的高度小于控件的高度isCheckTopAndBottom = false;dy = 0;}mScaleMatrix.postTranslate(dx, dy);//解决拖拽的时候左右 上下都会出现留白的情况checkBorderAndCenterWhenTranslate();setImageMatrix(mScaleMatrix);}}mLatX = x;//记录的值类似于断点续移,下次移动的时候从这个点开始mLastY = y;break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:mLastPointCount = 0;//抬起或者取消事件时候把这个置空break;}return true;}//----------------------手势implement OnScaleGestureListener------------------------///***处理图片缩放*/@Overridepublic boolean onScale(ScaleGestureDetector detector) {float scale = getScale();//当前相对于初始尺寸的缩放(之前matrix中获得)Log.e(TAG, "matrix scale---->" + scale);float scaleFactor = detector.getScaleFactor();//这个时刻缩放的/当前缩放尺度 (现在手势获取)Log.e(TAG, "scaleFactor---->" + scaleFactor);if (getDrawable() == null)return true;if ((scale < mMaxScale && scaleFactor > 1.0f) //放大|| (scale > mInitScale && scaleFactor < 1.0f)) {//缩小//如果要缩放的值比初始化还要小的话,就按照最小可以缩放的值进行缩放if (scaleFactor * scale < mInitScale){scaleFactor = mInitScale / scale;Log.e(TAG, "进来了1" + scaleFactor);}///如果要缩放的值比最大缩放值还要大,就按照最大可以缩放的值进行缩放if (scaleFactor * scale > mMaxScale){scaleFactor = mMaxScale / scale;Log.e(TAG, "进来了2---->" + scaleFactor);}Log.e(TAG, "scaleFactor2---->" + scaleFactor);//设置缩放比例mScaleMatrix.postScale(scaleFactor, scaleFactor,detector.getFocusX(), detector.getFocusY());//缩放中心是两手指之间checkBorderAndCenterWhenScale();//解决这种缩放导致缩放到最小时图片位置可能发生了变化// mScaleMatrix.postScale(scaleFactor, scaleFactor,
// getWidth() / 2, getHeight() / 2);//缩放中心是屏幕中心点setImageMatrix(mScaleMatrix);//通过手势给图片设置缩放}//返回值代表本次缩放事件是否已被处理。如果已被处理,那么detector就会重置缩放事件;// 如果未被处理,detector会继续进行计算,修改getScaleFactor()的返回值,直到被处理为止。// 因此,它常用在判断只有缩放值达到一定数值时才进行缩放return true;}@Overridepublic boolean onScaleBegin(ScaleGestureDetector detector) {//缩放开始一定要返回true该detector是否处理后继的缩放事件。返回false时,不会执行onScale()return true;}@Overridepublic void onScaleEnd(ScaleGestureDetector detector) {//缩放结束时}boolean once = true;/***图片初始化其大小 必须在onAttachedToWindow方法后才能获取宽高*/@Overridepublic void onGlobalLayout() {if (!once)return;Drawable d = getDrawable();if (d == null)return;//获取imageview宽高int width = getWidth();int height = getHeight();//获取图片宽高int imgWidth = d.getIntrinsicWidth();int imgHeight = d.getIntrinsicHeight();float scale = 1.0f;//如果图片的宽或高大于屏幕,缩放至屏幕的宽或者高if (imgWidth > width && imgHeight <= height)scale = (float) width / imgWidth;if (imgHeight > height && imgWidth <= width)scale = (float) height / imgHeight;//如果图片宽高都大于屏幕,按比例缩小if (imgWidth > width && imgHeight > height)scale = Math.min((float) imgWidth / width, (float) imgHeight / height);mInitScale = scale;//将图片移动至屏幕中心mScaleMatrix.postTranslate((width - imgWidth) / 2, (height - imgHeight) / 2);mScaleMatrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2);setImageMatrix(mScaleMatrix);once = false;}/*** 获取当前缩放比例*/public float getScale() {//Matrix为一个3*3的矩阵,一共9个值,复制到这个数组当中mScaleMatrix.getValues(mMartixValue);return mMartixValue[Matrix.MSCALE_X];//取出图片宽度的缩放比例}/*** 在缩放时,解决上下左右留白的情况*/private void checkBorderAndCenterWhenScale() {RectF rectF = getMatrixRectF();float deltaX = 0;float deltaY = 0;int width = getWidth();int height = getHeight();// 如果宽或高大于屏幕,则控制范围if (rectF.width() >= width) {if (rectF.left > 0) {deltaX = -rectF.left;//获取坐标留白的距离Log.e(TAG, "宽有问题1---->" +rectF.width()+"--"+rectF.left+"--"+width);}if (rectF.right < width) {//屏幕宽-屏幕已经占据的大小 得到右边留白的宽度deltaX = width - rectF.right;Log.e(TAG, "宽有问题2---->" +rectF.width()+"--"+rectF.left+"--"+width);}}if (rectF.height() >= height) {if (rectF.top > 0) {deltaY = -rectF.top;//同上,获取上面留白的距离}if (rectF.bottom < height) {//同上 获取下面留白的距离deltaY = height - rectF.bottom;}}// 如果宽或高小于屏幕,则让其居中if (rectF.width() < width) {//图片的中心点距离屏幕的中心点距离计算(画个图很明了)deltaX = width * 0.5f - rectF.right + 0.5f * rectF.width();Log.e(TAG, "宽有问题3---->" +rectF.width()+"--"+rectF.right+"结果"+deltaX);}if (rectF.height() < height) {deltaY = height * 0.5f - rectF.bottom + 0.5f * rectF.height();Log.e(TAG, "高有问题4---->" +rectF.height()+"--"+rectF.bottom+"结果"+deltaY);}mScaleMatrix.postTranslate(deltaX, deltaY);}/*** 获得图片放大缩小以后的宽和高,以及l,r,t,b*/private RectF getMatrixRectF() {Matrix rMatrix = mScaleMatrix;//获得当前图片的矩阵RectF rectF = new RectF();//创建一个空矩形Drawable d = getDrawable();if (d != null) {//使这个矩形的宽和高同当前图片一致//设置坐标位置(l和r是左边矩形的坐标点 tb是右边矩形的坐标点 lr设置为0就是设置为原宽高)rectF.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());//将矩阵映射到矩形上面,之后我们可以通过获取到矩阵的上下左右坐标以及宽高//来得到缩放后图片的上下左右坐标和宽高rMatrix.mapRect(rectF);//把坐标位置放入矩阵}return rectF;}/***判断是否可以拖动*/private boolean isMoveAction(float dx, float dy) {return Math.sqrt(dx * dx + dy * dy) > mTouchSlop;}/*** 放大移动的过程中解决上下左右留白的情况*/private void checkBorderAndCenterWhenTranslate() {RectF rectF = getMatrixRectF();float deltax = 0;float deltay = 0;int width = getWidth();int height = getHeight();//可以上下拖动且距离屏幕上方留白 根据Android系统坐标系往上移动的值要取负值if (rectF.top > 0 && isCheckTopAndBottom) {deltay = -rectF.top;Log.e(TAG, "上面留白距离---->" +rectF.top);}//可以上下拖动且距离屏幕底部留白 根据Android系统坐标系往下移动的值要取正值if (rectF.bottom < height && isCheckTopAndBottom) {deltay = height - rectF.bottom;Log.e(TAG, "下面留白距离---->" +rectF.bottom);}//可以左右拖动且左边留白 根据Android系统坐标系往左移动的值要取负值if (rectF.left > 0 && isCheckLeftAndRight) {deltax = -rectF.left;Log.e(TAG, "左边留白距离---->" +rectF.left);}//可以左右拖动且右边留白 根据Android系统坐标系往右移动的值要取正值if (rectF.right < width && isCheckLeftAndRight) {deltax = width - rectF.right;Log.e(TAG, "右边留白距离---->" +rectF.right);}mScaleMatrix.postTranslate(deltax, deltay);//处理偏移量}/*** View.postDelay()方法延时执行双击放大缩小 在主线程中运行 没隔16ms给用户产生过渡的效果的*/private class AutoScaleRunble implements Runnable {private float mTrgetScale;//缩放目标值private float x;//缩放中心点private float y;private float tempScale;//可能是BIGGER可能是SMALLERprivate float BIGGER = 1.07f;private float SMALLER = 0.93f;//构造传入缩放目标值,缩放的中心点public AutoScaleRunble(float mTrgetScale, float x, float y) {this.mTrgetScale = mTrgetScale;this.x = x;this.y = y;if (getScale() < mTrgetScale) {//双击放大//这个缩放比1f大就行 随便取个1.07tempScale = BIGGER;}if (getScale() > mTrgetScale) {//双击缩小//这个缩放比1f小就行 随便取个0.93tempScale = SMALLER;}}@Overridepublic void run() {//执行缩放mScaleMatrix.postScale(tempScale, tempScale, x, y);//在缩放时,解决上下左右留白的情况checkBorderAndCenterWhenScale();setImageMatrix(mScaleMatrix);//获取当前的缩放值float currentScale = getScale();//如果当前正在放大操作并且当前的放大尺度小于缩放的目标值,或者正在缩小并且缩小的尺度大于目标值//则再次延时16ms递归调用直到缩放到目标值if ((tempScale > 1.0f && currentScale < mTrgetScale) || (tempScale < 1.0f && currentScale > mTrgetScale)) {postDelayed(this, 16);} else {//代码走到这儿来说明不能再进行缩放了,可能放大的尺寸超过了mTrgetScale,//也可能缩小的尺寸小于mTrgetScale//所以这里我们mTrgetScale / currentScale 用目标缩放尺寸除以当前的缩放尺寸//得到缩放比,重新执行缩放到//mMidScale或者mInitScalefloat scale = mTrgetScale / currentScale;mScaleMatrix.postScale(scale, scale, x, y);checkBorderAndCenterWhenScale();setImageMatrix(mScaleMatrix);//执行完成后重置mIsAutoScaling = false;}}}
}
看完估计你也一脸懵逼了,这么多从哪儿开始入口看,那我们就从最基础的功能开始看,首先第一步我们要实现图片的手势放大缩小:我们写在构造方法中初始化ScaleGestureDetector这是一个专门处理多点触控点类,接着onAttachedToWindow()方法对view的布局初始化进行监听在其回调方法当中我们才可以在onGlobalLayout()方法中获取view的宽高并对图片固定住初始大小。第二步我们OnScaleGestureListener的回调方法对图片的宽高进行处理,调用mScaleMatrix.postScale实现图片的手势放大缩小。上述两步就实现了图片的手势放大缩小功能。
接着我们看进阶功能,同理第一步我们在onTouch方法中拿到触摸点的数量,计算触摸点的平均值,然后在移动的时候,得到dx ,dy 进行范围检查以后,调用mScaleMatrix.postTranslate进行设置偏移量完成图片放大后自由的移动,第二步我们还是在构造方法当中初始化GestureDetector这是一个我们用来监听双击手势的类,设置双击手势判断的临界值mMidScale,并利用postDelayed执行一个Runnable给用户一个过渡时间差,Runnable中再次根据的当前的缩放值继续执行。
最后我们考虑到和viewpager一起使用会有滑动冲突问题,所以我们要解决这个,主要看onTouch方法当我们图片的宽或高大于屏幕宽或高时,因为此时可以移动,我们不想被拦截。交给ZoomImageView处理:
getParent().requestDisallowInterceptTouchEvent(true);
执行了这段代码viewpager便不拦截事件,直接交给ZoomImageView处理事件,便也不会走onInterceptTouchEvent方法对事件进行拦截处理,比我们通过onInterceptTouchEvent方法来拦截事件处理的简单多了。
啰啰嗦嗦的一堆,我们看下效果图:
好了,还有重要的一点就是使用的时候设置的是src而不是bac,上面提到的”留白”自己可以去实践下才能理解,如果你对上述代码还有什么疑问,请留言尽管问,哈哈。
Android多点触控之ZoomImageView完全解析相关推荐
- 模拟Android多点触控
Android多点触控 Android多点触控 多点触控实现思路 第一种adb shell input方式 第二种adb shell sendevent方式 多点触控实现思路 经过资料的查询,要在 ...
- Android多点触控揭秘
本文原创,转载请注明:http://blog.csdn.net/cloudzfy1/article/details/6582707 Google 暑期大学生博客分享大赛 - 2011 Android ...
- Android多点触控详解
本文转载自GcsSloop的 安卓自定义View进阶-多点触控详解 的文章 Android 多点触控详解,在前面的几篇文章中我们大致了解了 Android 中的事件处理流程和一些简单的处理方案,本次带 ...
- Android多点触控技术
1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...
- Android 多点触控消息捕获与处理
1 简介 Android多点触控在本质上需要LCD驱动和程序本身设计上支持,目前市面上HTC.Motorola和Samsung等知名厂商只要使用电容屏触控原理的手机均可以支持多点触控Multitouc ...
- Android 多点触控 MotionEvent详解
相关API 介绍 MotionEvent.getY() 和 MotionEvent.getRawY() 的区别 getY 表示触摸事件在当前的View内的Y 坐标, getRawY表示触摸事件在整个屏 ...
- Android多点触控MultiTouch浅析
申明: 参考:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2013/0226/914.html 下面实现如何通过应用层支持多点触控操作, ...
- android 多点触控缩放,Android多点触控(图片的缩放Demo)
本文主要介绍Android的多点触控,使用了一个图片缩放的实例,来更好的说明其原理.需要实现OnTouchListener接口,重写其中的onTouch方法. 实现效果图: 源代码: 布局文件: ac ...
- 关于android多点触控
最近项目需要一个多点触控缩放的功能.然后上网查了下资料 总结一下: 首先android sdk版本很重要,比如你在AndroidManifest.xml中指定android:minSdkVersion ...
最新文章
- 机器学习误差分析(Error Analysis)实战
- GAN和PS合体会怎样?东京大学图像增强新研究:无需配对图像,增强效果还可解释...
- 程序员2004下载地址
- 一个很简单的淡入淡出相册 (转)
- 【design pattern】工厂方法模式和抽象工厂模式
- C#水晶报表,窗体不显示,闪退
- 基于CentOs的Hadoop集群全分布式部署
- 【前端】数字媒体技术专业主要课程及就业方向
- JavaScript正则表达式与注册验证
- Managing Configuration Data Programmatically in ASP.NET 2.0
- 未将引用设置到对象的实例
- git deamon 一个简单的git服务器
- 不懂域名系统,何谈网络编程
- 文件夹加密狗的加密原理与解密
- 新元宇宙奇科幻小说原创作品每周连载地球人奇游天球记第六回冬奥登月
- 正则表达式匹配书名号内容
- mysql停掉正在运行的存储过程
- python os库的常用函数记录
- Java并发包中常用类
- 图解如何用打印机套打快递单