欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android 在viewPager中双指缩放图片双击缩放图片单指拖拽图片的实现思路

程序员文章站 2023-12-04 17:54:04
我们就把这个问题叫做图片查看器吧,它的主要功能有: 1、双击缩放图片。 2、 双指缩放图片。 3、单指拖拽图片。 为此这个图片查看器需要考虑以下的技术点: 一、双...

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

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也一样继续拦截了事件。目前没解决。

以上所述是小编给大家介绍的在viewpager中双指缩放图片双击缩放图片单指拖拽图片的实现思路,希望对大家有所帮助