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

Android手势ImageView三部曲 第一部

程序员文章站 2023-12-14 21:04:40
这几天一直在研究github上的photoview跟gestureimageview,发现写的都很牛,看了很久的代码,于是打算把自己所看的一些东西总结一下,内容还是很多的,...

这几天一直在研究github上的photoview跟gestureimageview,发现写的都很牛,看了很久的代码,于是打算把自己所看的一些东西总结一下,内容还是很多的,但是很有含金量哈~~

先附上两个开源项目的链接:

gestureimageview:

photoview:https://github.com/chrisbanes/photoview

这样说有点乏味哈,先看看我们今天要实现的效果:

当一个手指按住图片的时候,此时的效果为拖拽的效果。
当两个手指按住图片的时候,手指旋转则图片跟着旋转,手指缩放则图片缩放。
效果图大致为(我模拟器不太好模拟旋转):

Android手势ImageView三部曲 第一部

好了下面我们来实现一下这个手势缩放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"
  />

最后运行代码:

Android手势ImageView三部曲 第一部

好了,虽说我们是实现了我们的手势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,感兴趣的小伙伴请继续关注哦。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。

上一篇:

下一篇: