我们就把这个问题叫做图片查看器吧,它的主要功能有:

(项目地址:https://github.com/TZHANHONG/ImageViewer/releases/tag/1.0,里面的MyImageView压缩包便是)

1、双击缩放图片。

2、 双指缩放图片。

3、单指拖拽图片。

为此这个图片查看器需要考虑以下的技术点:

一、双击缩放图片:

1、如果图片高度比屏幕的高度小得多,那么就将图片放大到高度与屏幕高度相等,否则就放大一个特定的倍数。

2、如何判断是否到达这个倍数来停止缩放。

3、判断完且停止放大后,图片可能已经超出了这个倍数需要的大小,如何回归到我们的目标大小。

4、判断完且停止缩小后,图片宽度可能已经小于屏幕宽度,在两边留下了空白,如何重置为原来的大小。

二、双指缩放图片:

1、双指缩放,放大一个特定的倍数停止。

2、如何判断是否到达这个倍数。

3、放大停止后,图片可能已经超出了这个倍数需要的大小,如何回归到我们的目标大小。

4、缩小停止后,图片宽度可能已经小于屏幕宽度,在两边留下了空白,如何重置为原来的大小。

三、单指拖拽:

1、当图片宽度小于或等于屏幕宽度的时候,禁止左右移动,当图片的高度小于屏幕高度的时候,禁止上下移动。

2、移动图片时,如果图片的一边已经与屏幕之间有了空白,松手后恢复,让图片的这一边与屏幕边界重合。

四、

如何判断是双击,还是多指触控,还是单指。

五、

如何解决与viewPager的滑动冲突,当图片已经滑动到尽头无法滑动时,此时viewPager应该拦截事件。

我们逐一来解决:

public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener {
 public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
setOnTouchListener(this);
/**
* 双击实现图片放大缩小
*/
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
changeViewSize(e);
return true;
}
});
}

在这里缩放图片是用matrix,因此首先要设置scaleType为matrix。
用手势判断双击行为。不要忘了在onTouch里面加上

if (mGestureDetector.onTouchEvent(event))
return true;

判断单指与多指触控,则在onTouch里面判断,要用 event.getAction() & MotionEvent.ACTION_MASK来判断。

//多指触控模式,单指,双指
private int mode; private final static int SINGLE_TOUCH = 1; //单指
private final static int DOUBLE_TOUCH = 2; //双指
@Override
public boolean onTouch(View view, MotionEvent event) {
rectF = getMatrixRectF();
if (mGestureDetector.onTouchEvent(event))
return true; switch (event.getAction() & event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mode = SINGLE_TOUCH;
break;
case MotionEvent.ACTION_MOVE:
if (mode >= DOUBLE_TOUCH) //双指缩放
{
}
if (mode == SINGLE_TOUCH) //单指拖拽
{
}
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_UP:
mode = 0;
break;
//在ACTION_MOVE中,事件被拦截了之后,有时候ACTION_UP无法触发,所以加上了ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
mode = 0;
break;
default:
break;
}
return true;
}

有如下事件使我们要用到的:

  • MotionEvent.ACTION_DOWN:在第一个点被按下时触发
  • MotionEvent.ACTION_UP:当屏幕上唯一的点被放开时触发
  • MotionEvent.ACTION_POINTER_DOWN:当屏幕上已经有一个点被按住,此时再按下其他点时触发。
  • MotionEvent.ACTION_POINTER_UP:当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
  • MotionEvent.ACTION_MOVE:当有点在屏幕上移动时触发。值得注意的是,由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动),所以实际的情况是,基本上只要有点在屏幕上,此事件就会一直不停地被触发。

在ACTION_MOVE中通过mode的大小来判断是单指还是双指。

不过有一个令人伤心的事情,Android自己有一个bug。经过测试发现双指交换触碰图片的时候,程序会闪退,出现异常:pointIndex out of range。这是Android自己的bug。个人觉得最好得解决方法是自定义一个viewPager,然后在里面重写:onTouchEvent,onInterceptTouchEvent,然后捕获异常。

   @Override
public boolean onTouchEvent(MotionEvent ev) {
try {
return super.onTouchEvent(ev);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
} @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
try {
return super.onInterceptTouchEvent(ev);
} catch (IllegalArgumentException ex) {
ex.printStackTrace();
}
return false;
}

这样程序就不会闪退了。

我们来看看双击放大的的代码:

    /**
* 双击缩放图片
*/
private void changeViewSize(MotionEvent e) { //获取双击的坐标
final float x = e.getX();
final float y = e.getY(); //如果此时还在缩放那就直接返回
if (animator != null && animator.isRunning())
return; //判断是处于放大还是缩小的状态
if (!isZoomChanged()) {
animator = ValueAnimator.ofFloat(1.0f, 2.0f);
} else {
animator = ValueAnimator.ofFloat(1.0f, 0.0f);
}
animator.setTarget(this);
animator.setDuration(500);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) { Float value = (Float) animator.getAnimatedValue();
matrix.postScale(value, value, x, y);
checkBorderAndCenterWhenScale(); //在缩放后让图片居中
setImageMatrix(matrix); /**
* 控制缩小的范围
* 如果已经小于初始大小,那么恢复到初始大小,然后停止
*/
if (checkRestScale()) {
matrix.set(oldMatrix);  //oldMatrix为最原始的matrix
setImageMatrix(matrix);
return;
}
/**
* 控制放大的范围
* 如果已经大于目标的放大倍数,那么直接置为目标的放大倍数
* 然后停止
*/
if (getMatrixValueX() >= mDoubleClickScale)
{
matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
});
}

判断处于放大还是缩小状态的代码:(不是初始值就说明是处于放大状态)

    /**
* 判断缩放级别是否是改变过
*
* @return true表示非初始值, false表示初始值
*/
private boolean isZoomChanged() {
float[] values = new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale = values[Matrix.MSCALE_X];
//获取初始时候的X轴缩放级别,两者做比较
oldMatrix.getValues(values);
return scale != values[Matrix.MSCALE_X];
}

getMatrixValue()的代码如下,是为了取得当前的放大倍数,相对于一开始的图片来说

 private float getMatrixValueX()
{
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale = values[Matrix.MSCALE_X];
//获取原始Matrix的X轴缩放级别
oldMatrix.getValues(values);
//返回放大的倍数
return scale / values[Matrix.MSCALE_X];
}

checkRestScale()的代码如下,主要是为了判断当前的缩放级别是否小于最初始的缩放级别。

    /**
* 判断是否需要重置
*
* @return 当前缩放级别小于原始缩放级别时,重置
*/
private boolean checkRestScale() {
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale = values[Matrix.MSCALE_X];
//获取原始的X轴缩放级别,两者做比较
oldMatrix.getValues(values);
return scale < values[Matrix.MSCALE_X];
}

checkBorderAndCenterWhenScale()的代码如下,否则图片缩放后位置会发生变化。

/**
* 在缩放时,进行图片显示范围的控制
*/
private void checkBorderAndCenterWhenScale()
{ RectF rect = getMatrixRectF();
float deltaX = 0;
float deltaY = 0; int width = getWidth();
int height = getHeight(); // 如果宽或高大于屏幕,则控制范围
if (rect.width() >= width)
{
if (rect.left > 0)
{
deltaX = -rect.left;
}
if (rect.right < width)
{
deltaX = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0)
{
deltaY = -rect.top;
}
if (rect.bottom < height)
{
deltaY = height - rect.bottom;
}
}
// 如果宽或高小于屏幕,则让其居中
if (rect.width() < width)
{
deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
}
if (rect.height() < height)
{
deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
} matrix.postTranslate(deltaX, deltaY);
setImageMatrix(matrix);
}

 

接下来看看双指缩放和单指拖拽:

@Override
public boolean onTouch(View view, MotionEvent event) {
rectF = getMatrixRectF(); //获取图片边界范围
if (mGestureDetector.onTouchEvent(event))
return true; switch (event.getAction() & event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//如果放大后图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
mode = SINGLE_TOUCH; x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (mode >= DOUBLE_TOUCH) //双指缩放
{
getParent().requestDisallowInterceptTouchEvent(true);
newDist = calculateDist(event); //计算距离
Point point = getMiPoint(event); //获取两手指间的中点坐标
if (newDist > oldDist + 1) //放大(加一是为了防止抖动)
{
changeViewSize(oldDist, newDist, point); //根据距离实现放大缩小
oldDist = newDist;
}
if (oldDist > newDist + 1) //缩小
{
changeViewSize(oldDist, newDist, point);
oldDist = newDist;
}
}
if (mode == SINGLE_TOUCH) //单指拖拽
{
float dx = event.getRawX() - x;
float dy = event.getRawY() - y; //如果移动过程中图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//如果向右移动图片到了尽头,那么就不要拦截事件,让viewPager处理
if (rectF.left >= 0 && dx > 0)
getParent().requestDisallowInterceptTouchEvent(false); //如果向左移动到了尽头,那么就不要拦截事件,让viewPager处理
if (rectF.right <= getWidth() && dx < 0)
getParent().requestDisallowInterceptTouchEvent(false); if (getDrawable() != null) {
//如果图片宽度或高度没有超出屏幕,那么就禁止左右或上下滑动
if (rectF.width() <= getWidth())
dx = 0;
if (rectF.height() < getHeight())
dy = 0; //如果图片向下移动到了尽头,不让它继续移动
if (rectF.top >= 0 && dy > 0)
dy = 0;
//如果图片向上移动到了尽头,不让它继续移动
if (rectF.bottom <= getHeight() && dy < 0)
dy = 0; //当移动距离大于1的时候再移动,因为ACTION_MOVE比较灵敏,
// 手指即使只是放在上面,依然能够检测到手指的抖动,然后让图片移动。
if (Math.abs(dx) > 1 || Math.abs(dy) > 1)
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
}
}
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;
oldDist = calculateDist(event); break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_UP:
backToPosition();
mode = 0;
break;
//在ACTION_MOVE中,事件被拦截了之后,有时候ACTION_UP无法触发,所以加上了ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
backToPosition();
mode = 0;
break;
default:
break;
}
return true;
}

首先先来看一个方法,根据图片的matrix获得图片的边界范围,这个范围映射在rect上。(这个范围检测是用在单指拖拽上的)

    /**
* 根据当前图片的Matrix获得图片的范围
*
* @return
*/
private RectF getMatrixRectF()
{
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect);
}
Log.e("aaaa",""+rect.bottom+" "+rect.left+" "+rect.right+" "+rect.top);
return rect;
}

rect.bottom:图片下边界的纵坐标

rect.left:图片左边界的横坐标

rect.right:图片右边界的横坐标

rect.top:图片上边界的纵坐标

rectF.width():图片宽度

rectF.height():图片高度

需要注意的是Matrix对图片的操作都是操作ImageView里面的bitmap,ImageView是没有变化的,上面所说的屏幕边界其实ImageView的边界,getWidth(),getHeight()是ImageView的宽和高。

方法 backToPosition()主要是实现单指拖拽的技术点2,当手指快速划过去的时候,在检测到无法继续滑动前图片边界与屏幕边界已经出现了距离,所以松开手指的时候要复位,让图片边界与屏幕边界重合。

/**
* 若是在移动后图片的边界脱离屏幕边界,那么就让图片边界与屏幕边界重合
* 若手指快速移动,停止后会出现图片距离屏幕有一段空白距离,然后经过判断不能再移动,
* 但是在进行下一次判断是否可以继续移动之前就已经出现了。
* 所以需要复位
*/
private void backToPosition() {
if (rectF.left >= 0) { //图片左边界与屏幕出现距离
matrix.postTranslate(-rectF.left, 0);
setImageMatrix(matrix);
}
if (rectF.right <= getWidth()) { //图片右边界与屏幕出现距离
matrix.postTranslate(getWidth() - rectF.right, 0);
setImageMatrix(matrix);
}
if (rectF.top >= 0) { //图片上边界与屏幕出现距离
matrix.postTranslate(0, -rectF.top);
setImageMatrix(matrix);
}
if (rectF.bottom <= getHeight()) { //图片下边界与屏幕出现距离
matrix.postTranslate(0, getHeight() - rectF.bottom);
setImageMatrix(matrix);
}
}

获取两手指间的中点坐标

    /**
* 获取双指缩放时候的缩放中点
*
* @return
*/
private Point getMiPoint(MotionEvent event) { float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1); mPoint.set((int) x / 2, (int) y / 2);
return mPoint;
}

计算两指触摸点的距离

    /**
* 计算两指触摸点之间的距离
*/
private float calculateDist(MotionEvent event) { float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y); }

双指缩放图片

    /**
* 双指缩放图片
*/
private void changeViewSize(float oldDist, float newDist, Point mPoint) {
float scale = newDist / oldDist; //缩放比例 matrix.postScale(scale, scale, mPoint.x, mPoint.y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix); //防止缩小的时候小于初始的图片大小,需要重置
reSetMatrix();
//如果缩放已经大于目标倍数,停止,因为有可能已经超出,那么就直接缩放到目标大小
if (getMatrixValueX() >= MAX_SCALE)
{
matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}

reSetMatrix()的代码如下:

    /**
* 重置Matrix
*/
private void reSetMatrix() {
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
}

checkRestScale()的代码在上面已经给出了。oldMatrix为最初始的Matrix。

到这里还没有结束,设置Imageview的ScaleType为Matrix,那么图片不会主动缩放到适应屏幕,也不会处于屏幕中间,因此我们的自定义ImageView需要继承ViewTreeObserver.OnGlobalLayoutListener

 @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
}
 @Override
public void onGlobalLayout() {
if (once)
{
Drawable d = getDrawable();
if (d == null)
return;
Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
int width = getWidth();
int height = getHeight();
// 拿到图片的宽和高
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
float scale = 1.0f;
// 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
if (dw > width && dh <= height)
{
scale = width * 1.0f / dw;
}
if (dh > height && dw <= width)
{
scale = height * 1.0f / dh;
}
// 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
if (dw > width && dh > height)
{
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
initScale = scale; Log.e("TAG", "initScale = " + initScale);
matrix.postTranslate((width - dw) / 2, (height - dh) / 2);
matrix.postScale(scale, scale, getWidth() / 2,
getHeight() / 2);
// 图片移动至屏幕中心
setImageMatrix(matrix); oldMatrix.set(getImageMatrix());
once = false; RectF rectF=getMatrixRectF();
setDoubleClickScale(rectF); }
}

// 拿到图片的宽和高
 int dw = d.getIntrinsicWidth();
 int dh = d.getIntrinsicHeight();
拿到的图片宽和高是bitmap的真实高度。

初始的oldMatrix就是在这里设置的,然后作为初始模板,代表着图片没被动手改变的Matrix。至于方法 setDoubleClickScale(rectF);只是设置双击放大的倍数而已,如果图片高度比屏幕的高度小得多,那么就将图片放大到高度与屏幕高度相等,否则就放大一个特定的倍数。必须在这里设置,因为在这里取到的rectF才能反映原始图片的边界,因为这时候还没有动手改变图片。

    /**
* 设置双击放大的倍数
*/
private void setDoubleClickScale(RectF rectF)
{
if(rectF.height()<getHeight()-100)
{
mDoubleClickScale=getHeight()/rectF.height();
}
else
mDoubleClickScale=2f;
}

到这里大概结束了,下面就贴出完整的代码:

package com.example.tangzh.myimageview;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.ImageView; /**
* Created by TangZH on 2017/5/3.
*/
public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener {
private final static int SINGLE_TOUCH = 1; //单指
private final static int DOUBLE_TOUCH = 2; //双指 //多指触控模式,单指,双指
private int mode; //两指触碰点之间的距离
private float oldDist;
private float newDist; /**
* 最大缩放级别
*/
private static final float MAX_SCALE = 5f;
/**
* 双击时的缩放级别
*/
private float mDoubleClickScale = 2; /**
* 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于0
*/
private float initScale = 1.0f;
private boolean once = true;
private RectF rectF; /**
* 用于双击检测
*/
private GestureDetector mGestureDetector;
private int x = 0;
private int y = 0; private Point mPoint = new Point(); private final Matrix matrix = new Matrix();
private Matrix oldMatrix = new Matrix(); private ValueAnimator animator; public MyImageView(Context context) {
this(context, null);
} public MyImageView(Context context, AttributeSet attrs) {
super(context, attrs);
super.setScaleType(ScaleType.MATRIX);
setOnTouchListener(this);
/**
* 双击实现图片放大缩小
*/
mGestureDetector = new GestureDetector(context,
new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onDoubleTap(MotionEvent e) {
changeViewSize(e);
return true;
}
});
} @Override
public boolean onTouch(View view, MotionEvent event) {
rectF = getMatrixRectF(); //获取图片边界范围
if (mGestureDetector.onTouchEvent(event))
return true; switch (event.getAction() & event.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
//如果放大后图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
mode = SINGLE_TOUCH; x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
if (mode >= DOUBLE_TOUCH) //双指缩放
{
getParent().requestDisallowInterceptTouchEvent(true);
newDist = calculateDist(event); //计算距离
Point point = getMiPoint(event); //获取两手指间的中点坐标
if (newDist > oldDist + 1) //放大(加一是为了防止抖动)
{
changeViewSize(oldDist, newDist, point); //根据距离实现放大缩小
oldDist = newDist;
}
if (oldDist > newDist + 1) //缩小
{
changeViewSize(oldDist, newDist, point);
oldDist = newDist;
}
}
if (mode == SINGLE_TOUCH) //单指拖拽
{
float dx = event.getRawX() - x;
float dy = event.getRawY() - y; //如果移动过程中图片的边界超出了屏幕,那么就拦截事件,不让viewPager处理
if (rectF.width() > getWidth() || rectF.height() > getHeight()) {
getParent().requestDisallowInterceptTouchEvent(true);
}
//如果向右移动图片到了尽头,那么就不要拦截事件,让viewPager处理
if (rectF.left >= 0 && dx > 0)
getParent().requestDisallowInterceptTouchEvent(false); //如果向左移动到了尽头,那么就不要拦截事件,让viewPager处理
if (rectF.right <= getWidth() && dx < 0)
getParent().requestDisallowInterceptTouchEvent(false); if (getDrawable() != null) {
//如果图片宽度或高度没有超出屏幕,那么就禁止左右或上下滑动
if (rectF.width() <= getWidth())
dx = 0;
if (rectF.height() < getHeight())
dy = 0; //如果图片向下移动到了尽头,不让它继续移动
if (rectF.top >= 0 && dy > 0)
dy = 0;
//如果图片向上移动到了尽头,不让它继续移动
if (rectF.bottom <= getHeight() && dy < 0)
dy = 0; //当移动距离大于1的时候再移动,因为ACTION_MOVE比较灵敏,
// 手指即使只是放在上面,依然能够检测到手指的抖动,然后让图片移动。
if (Math.abs(dx) > 1 || Math.abs(dy) > 1)
matrix.postTranslate(dx, dy);
setImageMatrix(matrix);
}
}
x = (int) event.getRawX();
y = (int) event.getRawY();
break;
case MotionEvent.ACTION_POINTER_DOWN:
mode += 1;
oldDist = calculateDist(event);
Log.e("q", "" + "a"); Log.e(":::", "" + event.getPointerCount() + " " + event.getActionIndex() + " " + event.findPointerIndex(0));
break;
case MotionEvent.ACTION_POINTER_UP:
mode -= 1;
break;
case MotionEvent.ACTION_UP:
backToPosition();
mode = 0;
break;
//在ACTION_MOVE中,事件被拦截了之后,有时候ACTION_UP无法触发,所以加上了ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
backToPosition();
mode = 0;
break;
default:
break;
}
return true;
} /**
* 计算两指触摸点之间的距离
*/
private float calculateDist(MotionEvent event) { float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y); } @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
getViewTreeObserver().addOnGlobalLayoutListener(this);
} /**
* 若是在移动后图片的边界脱离屏幕边界,那么就让图片边界与屏幕边界重合
* 若手指快速移动,停止后会出现图片距离屏幕有一段空白距离,然后经过判断不能再移动,
* 但是在进行下一次判断是否可以继续移动之前就已经出现了。
* 所以需要复位
*/
private void backToPosition() {
if (rectF.left >= 0) { //图片左边界与屏幕出现距离
matrix.postTranslate(-rectF.left, 0);
setImageMatrix(matrix);
}
if (rectF.right <= getWidth()) { //图片右边界与屏幕出现距离
matrix.postTranslate(getWidth() - rectF.right, 0);
setImageMatrix(matrix);
}
if (rectF.top >= 0) { //图片上边界与屏幕出现距离
matrix.postTranslate(0, -rectF.top);
setImageMatrix(matrix);
}
if (rectF.bottom <= getHeight()) { //图片下边界与屏幕出现距离
matrix.postTranslate(0, getHeight() - rectF.bottom);
setImageMatrix(matrix);
}
} /**
* 获取双指缩放时候的缩放中点
*
* @return
*/
private Point getMiPoint(MotionEvent event) { float x = event.getX(0) + event.getX(1);
float y = event.getY(0) + event.getY(1); mPoint.set((int) x / 2, (int) y / 2);
return mPoint;
} /**
* 双指缩放图片
*/
private void changeViewSize(float oldDist, float newDist, Point mPoint) {
float scale = newDist / oldDist; //缩放比例 matrix.postScale(scale, scale, mPoint.x, mPoint.y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix); //防止缩小的时候小于初始的图片大小,需要重置
reSetMatrix();
//如果缩放已经大于目标倍数,停止,因为有可能已经超出,那么就直接缩放到目标大小
if (getMatrixValueX() >= MAX_SCALE)
{
matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
} /**
* 双击缩放图片
*/
private void changeViewSize(MotionEvent e) { //获取双击的坐标
final float x = e.getX();
final float y = e.getY(); //如果此时还在缩放那就直接返回
if (animator != null && animator.isRunning())
return; //判断是处于放大还是缩小的状态
if (!isZoomChanged()) {
animator = ValueAnimator.ofFloat(1.0f, 2.0f);
} else {
animator = ValueAnimator.ofFloat(1.0f, 0.0f);
}
animator.setTarget(this);
animator.setDuration(500);
animator.setInterpolator(new DecelerateInterpolator());
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) { Float value = (Float) animator.getAnimatedValue();
matrix.postScale(value, value, x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix); /**
* 控制缩小的范围
* 如果已经小于初始大小,那么恢复到初始大小,然后停止
*/
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
/**
* 控制放大的范围
* 如果已经大于目标的放大倍数,那么直接置为目标的放大倍数
* 然后停止
*/
if (getMatrixValueX() >= mDoubleClickScale)
{
matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y);
checkBorderAndCenterWhenScale();
setImageMatrix(matrix);
return;
}
}
});
} /**
* 判断缩放级别是否是改变过
*
* @return true表示非初始值, false表示初始值
*/
private boolean isZoomChanged() {
float[] values = new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale = values[Matrix.MSCALE_X];
//获取模板的X轴缩放级别,两者做比较
oldMatrix.getValues(values);
return scale != values[Matrix.MSCALE_X];
} /**
* 重置Matrix
*/
private void reSetMatrix() {
if (checkRestScale()) {
matrix.set(oldMatrix);
setImageMatrix(matrix);
return;
}
} /**
* 设置双击放大的倍数
*/
private void setDoubleClickScale(RectF rectF)
{
if(rectF.height()<getHeight()-100)
{
mDoubleClickScale=getHeight()/rectF.height();
}
else
mDoubleClickScale=2f;
} /**
* 判断是否需要重置
*
* @return 当前缩放级别小于模板缩放级别时,重置
*/
private boolean checkRestScale() {
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale = values[Matrix.MSCALE_X];
//获取模板的X轴缩放级别,两者做比较
oldMatrix.getValues(values);
return scale < values[Matrix.MSCALE_X];
} private float getMatrixValueX()
{
// TODO Auto-generated method stub
float[] values = new float[9];
getImageMatrix().getValues(values);
//获取当前X轴缩放级别
float scale = values[Matrix.MSCALE_X];
//获取模板的X轴缩放级别,两者做比较
oldMatrix.getValues(values);
return scale / values[Matrix.MSCALE_X];
}
/**
* 在缩放时,进行图片显示范围的控制
*/
private void checkBorderAndCenterWhenScale()
{ RectF rect = getMatrixRectF();
float deltaX = 0;
float deltaY = 0; int width = getWidth();
int height = getHeight(); // 如果宽或高大于屏幕,则控制范围
if (rect.width() >= width)
{
if (rect.left > 0)
{
deltaX = -rect.left;
}
if (rect.right < width)
{
deltaX = width - rect.right;
}
}
if (rect.height() >= height)
{
if (rect.top > 0)
{
deltaY = -rect.top;
}
if (rect.bottom < height)
{
deltaY = height - rect.bottom;
}
}
// 如果宽或高小于屏幕,则让其居中
if (rect.width() < width)
{
deltaX = width * 0.5f - rect.right + 0.5f * rect.width();
}
if (rect.height() < height)
{
deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height();
}
Log.e("TAG", "deltaX = " + deltaX + " , deltaY = " + deltaY); matrix.postTranslate(deltaX, deltaY);
setImageMatrix(matrix);
}
/**
* 根据当前图片的Matrix获得图片的范围
*
* @return
*/
private RectF getMatrixRectF()
{
RectF rect = new RectF();
Drawable d = getDrawable();
if (null != d)
{
rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
matrix.mapRect(rect); //如果没有这个,那么下面Log的输出将会与上一句的一样。
}
Log.e("aaaa",""+rect.bottom+" "+rect.left+" "+rect.right+" "+rect.top);
return rect;
} @Override
public void onGlobalLayout() {
if (once)
{
Drawable d = getDrawable();
if (d == null)
return;
Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight());
int width = getWidth();
int height = getHeight();
// 拿到图片的宽和高
int dw = d.getIntrinsicWidth();
int dh = d.getIntrinsicHeight();
float scale = 1.0f;
// 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
if (dw > width && dh <= height)
{
scale = width * 1.0f / dw;
}
if (dh > height && dw <= width)
{
scale = height * 1.0f / dh;
}
// 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
if (dw > width && dh > height)
{
scale = Math.min(width * 1.0f / dw, height * 1.0f / dh);
}
initScale = scale; Log.e("TAG", "initScale = " + initScale);
matrix.postTranslate((width - dw) / 2, (height - dh) / 2);
matrix.postScale(scale, scale, getWidth() / 2,
getHeight() / 2);
// 图片移动至屏幕中心
setImageMatrix(matrix); oldMatrix.set(getImageMatrix());
once = false; RectF rectF=getMatrixRectF();
setDoubleClickScale(rectF); }
}
}

唉,虽然已经写完了,但是还有一个问题没有解决,就是移动图片到尽头,这时候不要放手,往反方向移动,就会出现一个问题,图片反方向的部分被遮挡,无法看到,然后移动的时候是直接切换图片,而不再继续移动图片,这个问题的原因是:当你移动图片到尽头时,就把事件交给viewpager来处理了,即使再往反方向移动图片,viewPager也一样继续拦截了事件。目前没解决。

参考博客:http://blog.csdn.net/lmj623565791/article/details/39480503

http://blog.csdn.net/nangongyanya/article/details/50697217

在viewPager中双指缩放图片,双击缩放图片,单指拖拽图片的更多相关文章

  1. div中粘贴图片并上传服务器 div中拖拽图片文件并上传服务器

    应用简介:此文主要是描述如何在前端div中直接ctrl+v 粘贴图片,并上传到服务器,包括拖拽图片文件到div中 应用场景描述:用QQ或者其它切图软件截图,在指定的div中ctrl+v 粘贴并显示,点 ...

  2. 基于html5可拖拽图片循环滚动切换

    分享一款基于html5可拖拽图片循环滚动切换.这是一款支持手机端拖拽切换的网站图片循环滚动特效.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div id="s ...

  3. android 拖拽图片&拖动浮动按钮到处跑

    来自老外: 拖拽图片效果 方法一: 布局文件 <?xml version="1.0" encoding="utf-8"?> <LinearLa ...

  4. C# 图片盖章功能实现,支持拖拽-旋转-放缩-保存

    实现图片盖章功能,在图片上点击,增加“图章”小图片,可以拖拽“图章”到任意位置,也可以点击图章右下角园框,令图片跟着鼠标旋转和放缩. 操作方法:1.点击增加“图章”2.选中移动图标3.点中右下角放缩旋 ...

  5. h5图片上传预览与拖拽上传

    图片上传: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w ...

  6. 模拟拖拽图片 碰撞检测 DOM 鼠标事件 闭包

    <!doctype html><html lang="en"> <head> <meta charset="UTF-8" ...

  7. 拖拽图片到另一个div里

    HTML代码 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UT ...

  8. Jquery 多行拖拽图片排序 jq优化

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  9. js 禁止右击保存图片,禁止拖拽图片

    禁止鼠标右键保存图片 <img src="" oncontextmenu="return false;"> 禁止鼠标拖动图片 <img src ...

随机推荐

  1. java IO流之详细总结

    什么是io流? 分为两种: 输入流:可以从文件中读取到程序,从源数据源读取到程序,叫做输入流. 输出流:可以从程序中读取到文件,从程序写,使用输出流,写入到文件中.叫做输出流. 使用File操作文件或 ...

  2. Jade是变体的HTML

    在这段HTML代码中,div 包含了一个 a 元素与一段没有标记包围的文本.若要用Jade表述这段HTML,div 元素和 a 元素都可以用前面所述的方法实现,但剩下的那个没有标记包围的文本就不能用前 ...

  3. 解决spring的bean同名冲突

    今天工作发现当不同模块下有相同bean然后又被同一个模块引用的话就会导致bean同名冲突,如下: 解决方案很简单,如果是xml配置直接修改bean的名称即可,如果是注解形式修改如下: 只要在servi ...

  4. sql server 性能调优之 资源等待内存瓶颈的三种等待类型

    一.概述 这篇介绍Stolen内存相关的主要三种等待类型以及对应的waittype编号,CMEMTHREAD(0x00B9),SOS_RESERVEDMEMBLOCKLIST(0x007B),RESO ...

  5. 全网最详细的一款满足多台电脑共用一个鼠标和键盘的工具Synergy(图文详解)

    不多说,直接上干货! 前言 如今无论你是在公司做大数据开发还是实验室里搞科研,这个软件确实好用,作为正在通往大数据架构师路上的我们没有几台电脑怎么行?台式机.笔记本,都放在写字台上,笔记本内置键盘鼠标 ...

  6. CentOS7.0小随笔——运行级别

    一.Linux运行级别(通用) 0:关机(halt) 1:单用户模式(无需用户名和密码的登录,用于紧急维护系统时用,类似于Windows中的安全模式) 2:不启用网络功能的多用户模式 3:启用网络功能 ...

  7. 精读《dob - 框架使用》

    本系列分三部曲:<框架实现> <框架使用> 与 <跳出框架看哲学>,这三篇是我对数据流阶段性的总结,正好补充之前过时的文章. 本篇是 <框架使用>. 1 ...

  8. Spring Boot 系列(九)数据层-集成Spring-data-jpa

    实际开发中,不可避免地会对数据进行反复的增删改查操作,然而这部分工作是十分繁琐枯燥的.那么,随即而生的ORM框架就能很好的解决这个问题. 我们常用的ORM框架有:Hibernate.Mybatis.J ...

  9. 【转载】LINUX 和 WINDOWS 内核的区别

    LINUX 和 WINDOWS 内核的区别 [声明:欢迎转载,转载请注明出自CU ACCESSORY http://linux.chinaunix.net/bbs/thread-1153868-1-1 ...

  10. 痞子衡嵌入式:开源软件协议(MIT/BSD/Apache/LGPL/MPL/GPL)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家讲的是关于开源软件协议基本知识. 牛顿曾说过:"如果我比别人看得更远,那是因为我站在巨人的肩上".在软件开发中如果说也存在巨 ...