可拖拽和可缩放的ImageView

ZoomImageView

一、简介

​ 一个支持手势拖拽、缩放的ImageView。
Github地址:https://github.com/Fiend96/ZoomImageView

​ 手势操作是智能手机的一大特色,特别是多点手势有着良好的交互体验。手势操纵图片广泛应用于Android的各大app,包括QQ、微信和微博等。本项目正是基于上述app的体验作为参考而开发的一个控件。其可以支持拖动、点击、双点滑动等手势操纵图片的变换。

二、主要实现技术

  • OnTouchListener触摸事件:处理多点触摸操作
  • GestureDetector手势辅助类:处理滑动、单击和快速滑动(滑动后有惯性)事件

  • Matrix变化:实现图片的缩放和移动效果,需要设置

    super.setScaleType(ScaleType.MATRIX);
    

    然后通过下述几种变化实现缩放和移动效果:

    postScale(scaleX, scaleY);//改变大小
    
    postTranslate(tranX, tranY);//改变位置
    
    setImageMatrix(matrix);//ImageView的方法,设置当前图片的矩阵
    
  • ValueAnimator实现动画效果,有以下情况需要动画

    • 缩放倍数小于正常倍数时需要恢复正常倍数
    • 拖拽时超过了边界时需要恢复与边界重合
  • 详细实现请看代码

三、实现细节

1、操作模式

​ 判断处于那个状态而进行不同的操作

int NONE = 0;//无法操作
int DRAG = 1;//拖动模式,在放大的情况下
int ZOOM = 2;//缩放模式
int DOUBLECLICK = 4;//双击模式
2、大小模式

​ 需要判断不同的大小来决定操作模式

int ENLARGE = 5;//放大模式
int NARROW = 6;//缩小模式
float MAX_SCALE = 4f;//最大放大倍数
float MIN_SCALE = 0.5f;//最小缩放倍数
3、拖动

​ 判断是否可以进入拖拽模式

if (sizeMode == ENLARGE || ableTranY) {// 放大或Y轴可拖动
    mode = DRAG;// 单点进入拖动模式
    startPoint.set(e.getX(), e.getY());//保存开始拖动时点的数据
    lastPoint.set(e.getX(), e.getY());
}

​ 在GestureDetector的onSroll方法中处理拖动效果

if (mode == DRAG) {// 拖动模式
    mCurrentMatrix.set(mSaveMatrix);//每次拖动都是基于开始拖动时候的参数来计算的
    dragImage(e2);//处理拖动
}

​ 拖动的具体操作

float currentX = event.getX();// 获取当前的x坐标
float currentY = event.getY();// 获取当前的Y坐标
float tranX = currentX - startPoint.x;// 移动的x轴距离
float tranY = currentY - startPoint.y;// 移动的y轴距离
mCurrentMatrix.postTranslate(tranX, tranY);
setImageMatrix(mCurrentMatrix);
4、缩放

​ 判断是否可以进入缩放状态

case MotionEvent.ACTION_POINTER_DOWN: //第二次down事件,表示两点同时触摸
    getSizeMode();
    oldDistance = spacing(event); //计算两点的距离
    if (oldDistance > 10f) { //两点超过10才可以拖动
        mode = ZOOM;
    }

​ 在OnTouchListener中进行缩放操作

case MotionEvent.ACTION_MOVE:
    if (mode == ZOOM) {// 缩放模式
        mCurrentMatrix.set(mSaveMatrix);//每次缩放都是基于开始缩放时候的参数来计算的
        zoomImage(event);
    }

​ 缩放的具体操作

float newDistance = spacing(event);// 获取新的距离
float scale = newDistance / oldDistance;// 缩放比例
scale = getScaleType(scale);// 获取缩放的类型
midPoint(midPoint, event);//计算中点位置,根据中点位置来进行缩放
mCurrentMatrix.postScale(scale, scale, midPoint.x, midPoint.y);
setImageMatrix(mCurrentMatrix);
5、恢复动画

​ 在缩放倍数小于正常倍数(初始化的倍数)时需要恢复到正常倍数。在拖拽图片超出了边界也需要恢复到不超出边界。显然,只有在手指离开屏幕的时候才会需要恢复,所以需要监控UP事件来判断是否需要恢复

case MotionEvent.ACTION_UP:
    // 拖拽模式
    if (mode == DRAG) {
        after();
        mode = NONE;
        // 缩放模式
    } else if (mode == ZOOM) {
        after();
        mode = NONE;
    } else if (mode == DOUBLECLICK) {
        mode = NONE;
    }

​ 恢复正常状态的操作就是判断倍数是否小于正常倍数

if (mScale < getNolmalScale()) {
  //处理
}

​ 和是否拖出边界

if (mTranX > 0) { //拖出左边界
    //处理
} else if (Math.abs(mTranX) > imageWidth - mViewWidth) {//拖出右边界
    //处理
}

// 对Y轴移动处理,若图片高度小于控件高度则无法进行Y轴拖动(图片宽度一定大于或等于控件宽度)
if (imageHeight > mViewHeight) {
  if (mTranY > 0) {// 拖出上边界
      //处理
  } else if (Math.abs(mTranY) > imageHeight - mViewHeight) { //拖出下边界
      //处理
  }
} else if (imageHeight < mViewHeight) { //无法进行拖动
    //处理
}
6、恢复初始化状态

​ 默认双击恢复初始化状态,也可以调用方法来恢复初始化状态。具体计算与拖拽缩放类似,这里不做更多解释,详细请看代码。

四、解决与ViewPager的滑动冲突

​ 在ViewPager中使用该控件需要判断什么时候ViewPager需要处理事件(翻页)。这里是在拖动滑出边界时将事件交给ViewPager来处理,使用的接口为

//isInterrupt true时父View不拦截事件,false时View可以拦截事件
parent.requestDisallowInterceptTouchEvent(isInterrupt);

​ 因为事件需要从父布局中传入,所以必须在DOWN事件(父布局无法拦截DOWN事件)中通知父布局不拦截事件

setInterrupt(true);
if (sizeMode == NONE && !ableTranY) { 
    // 判断sizeMode是因为初始化状态只能进行缩放操作
    // ableTranY表示Y轴可拖动,因为初始化时图片宽度于控件宽度相同,所以X轴无法拖动,若图片Y轴可拖动同样可进行拖动操作
    setInterrupt(false);
}

​ 在拖动过程中判断是否将事件交给父布局

//isInterruptLeft和isInterruptRight为可设置的参数,表示强制拦截
boolean ableToTurnLeft = mTranX > 3 && isInterruptLeft;
boolean ableToTurnRight = Math.abs(mTranX) > Math.abs(mImageWidth - mViewWidth) + 3 && isInterruptRight;
//将滑动控制权返回给父view同时调整位置
if (ableToTurnLeft) {
    values[Matrix.MTRANS_X] = 0; //将图片恢复到不超过边界的状态
    setInterrupt(false);
    mCurrentMatrix.setValues(values);
} else if (ableToTurnRight) {
    values[Matrix.MTRANS_X] = mViewWidth - mImageWidth;//将图片恢复到不超过边界的状态
    mCurrentMatrix.setValues(values);
    setInterrupt(false);
} 

五、参考

    原文作者:科学的超bug
    原文地址: https://www.jianshu.com/p/cdf30089467a
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞