Android实现手势滑动多点触摸缩放平移图片效果

时间:2021-09-24 14:45:37

现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位。
一、概述
想要做到图片支持多点触控,*的进行缩放、平移,需要了解几个知识点:matrix , gesturedetector , scalegesturedetector 以及事件分发机制,ps:不会咋办,不会你懂的。
1、matrix
矩阵,看深入了都是3维矩阵的乘啊什么的,怪麻烦的~~
其实这么了解下就行了:
matrix
数据结构3维矩阵;
内部存储:new float[9] ; 内部就是个一维数组,内部9个元素;可以进行setvalues(float[] values)进行初始化
每个元素代表的意思:

?
1
2
3
4
5
{
    mscale_x, mskew_x, mtrans_x, 
        mskew_y, mscale_y, mtrans_y, 
        mpersp_0, mpersp_1, mpersp_2 
}; 

字面上,应该能看出来哪个代表x方向缩放,哪个代表垂直方向的偏移量吧~~有不认识的3个,没事,请无视。
操作
比如你想要设置matrix的偏移量为200,100
你可以这么写:

?
1
2
3
matrix transmatrix = new matrix();
    float[] values = new float[] { 1.0, 0, 200, 0, 1.0, 100, 0, 0, 1.0 };
    transmatrix.setvalues(values);

如果需要在旋转30度,放大两倍~~
这么写其实怪麻烦的~~
matrix提供了一些常用的api:例如我们可以这么写:

?
1
2
matrix transmatrix = new matrix();
    transmatrix.posttranslate(200, 100);

Android实现手势滑动多点触摸缩放平移图片效果

如何获取值:
当然了,我们对一个matrix进行了各种操作,一会postscale,一会posttranslate;那么现在如何获得当前的缩放比例:
前面说setvalues可以初始化,那么getvalues就能拿到当前矩阵的值,拿到的是个一维数组,9个元素;再通过下标取对应值就可以。
比如我想知道现在x方向缩放比例:

?
1
2
3
4
5
public final float getscale()
  {
    scalematrix.getvalues(matrixvalues);
    return matrixvalues[matrix.mscale_x];
  }

好了,知道这些就够了~~
2、gesturedetector
嗯,自己看api,能够捕捉到长按、双击什么的;用法会在例子中
3、scalegesturedetector
嗯,有点像继承来的,其实不是的,独立的一个类~用于检测缩放的手势~~~用法会在例子中
二、实战
为了大家更好的理解,我会独立出每个功能,最后再整合到一起~~也方面大家对每个api的使用的学习。
1、*的缩放
需求:当图片加载时,将图片在屏幕中居中;图片宽或高大于屏幕的,缩小至屏幕大小;*对图片进行方法或缩小;
代码不是很长,直接贴代码了

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
package com.zhy.view;
 
import android.content.context;
import android.graphics.matrix;
import android.graphics.drawable.drawable;
import android.util.attributeset;
import android.util.log;
import android.view.motionevent;
import android.view.scalegesturedetector;
import android.view.scalegesturedetector.onscalegesturelistener;
import android.view.view;
import android.view.view.ontouchlistener;
import android.view.viewtreeobserver;
import android.widget.imageview;
 
public class zoomimageview extends imageview implements onscalegesturelistener,
    ontouchlistener, viewtreeobserver.ongloballayoutlistener
 
{
  private static final string tag = zoomimageview.class.getsimplename();
   
  public static final float scale_max = 4.0f;
  /**
   * 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于0
   */
  private float initscale = 1.0f;
 
  /**
   * 用于存放矩阵的9个值
   */
  private final float[] matrixvalues = new float[9];
 
  private boolean once = true;
 
  /**
   * 缩放的手势检测
   */
  private scalegesturedetector mscalegesturedetector = null;
 
  private final matrix mscalematrix = new matrix();
 
  public zoomimageview(context context)
  {
    this(context, null);
  }
 
  public zoomimageview(context context, attributeset attrs)
  {
    super(context, attrs);
    super.setscaletype(scaletype.matrix);
    mscalegesturedetector = new scalegesturedetector(context, this);
    this.setontouchlistener(this);
  }
 
  @override
  public boolean onscale(scalegesturedetector detector)
  {
    float scale = getscale();
    float scalefactor = detector.getscalefactor();
 
    if (getdrawable() == null)
      return true;
 
    /**
     * 缩放的范围控制
     */
    if ((scale < scale_max && scalefactor > 1.0f)
        || (scale > initscale && scalefactor < 1.0f))
    {
      /**
       * 最大值最小值判断
       */
      if (scalefactor * scale < initscale)
      {
        scalefactor = initscale / scale;
      }
      if (scalefactor * scale > scale_max)
      {
        scalefactor = scale_max / scale;
      }
      /**
       * 设置缩放比例
       */
      mscalematrix.postscale(scalefactor, scalefactor, getwidth() / 2,
          getheight() / 2);
      setimagematrix(mscalematrix);
    }
    return true;
 
  }
 
  @override
  public boolean onscalebegin(scalegesturedetector detector)
  {
    return true;
  }
 
  @override
  public void onscaleend(scalegesturedetector detector)
  {
  }
 
  @override
  public boolean ontouch(view v, motionevent event)
  {
    return mscalegesturedetector.ontouchevent(event);
 
  }
 
   
  /**
   * 获得当前的缩放比例
   *
   * @return
   */
  public final float getscale()
  {
    mscalematrix.getvalues(matrixvalues);
    return matrixvalues[matrix.mscale_x];
  }
 
  @override
  protected void onattachedtowindow()
  {
    super.onattachedtowindow();
    getviewtreeobserver().addongloballayoutlistener(this);
  }
 
  @suppresswarnings("deprecation")
  @override
  protected void ondetachedfromwindow()
  {
    super.ondetachedfromwindow();
    getviewtreeobserver().removeglobalonlayoutlistener(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(dw * 1.0f / width, dh * 1.0f / height);
      }
      initscale = scale;
      // 图片移动至屏幕中心
            mscalematrix.posttranslate((width - dw) / 2, (height - dh) / 2);
      mscalematrix
          .postscale(scale, scale, getwidth() / 2, getheight() / 2);
      setimagematrix(mscalematrix);
      once = false;
    }
 
  }
 
}

1)、我们在ongloballayout的回调中,根据图片的宽和高以及屏幕的宽和高,对图片进行缩放以及移动至屏幕的中心。如果图片很小,那就正常显示,不放大了~
2)、我们让ontouchlistener的motionevent交给scalegesturedetector进行处理

?
1
2
3
4
5
6
@override
  public boolean ontouch(view v, motionevent event)
  {
    return mscalegesturedetector.ontouchevent(event);
 
  }

3)、在onscale的回调中对图片进行缩放的控制,首先进行缩放范围的判断,然后设置mscalematrix的scale值
现在的效果:
1、小于屏幕的宽和高

Android实现手势滑动多点触摸缩放平移图片效果

2、大于屏幕的宽和高

Android实现手势滑动多点触摸缩放平移图片效果

可是,可是,存在问题:

  • 1、缩放的中心点,我们设置是固定的,屏幕中间
  • 2、放大后,无法移动

下面,我们先解决缩放的中心点问题,不能一直按屏幕中心么,像我这样的,我比较关注妹子的眼睛,我要放大那一块~~~
2、设置缩放中心
1、单纯的设置缩放中心
仅仅是设置中心很简单,直接修改下中心点 :

?
1
2
3
4
5
6
/**
       * 设置缩放比例
       */
      mscalematrix.postscale(scalefactor, scalefactor,
          detector.getfocusx(), detector.getfocusx());
      setimagematrix(mscalematrix);

但是,随意的中心点放大、缩小,会导致图片的位置的变化,最终导致,图片宽高大于屏幕时,图片与屏幕间出现白边;图片小于屏幕,但是不居中。
2、控制缩放时图片显示的范围
所以我们在缩放的时候需要手动控制下范围:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
   * 在缩放时,进行图片显示范围的控制
   */
  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);
 
    mscalematrix.posttranslate(deltax, deltay);
 
  }
 
  /**
   * 根据当前图片的matrix获得图片的范围
   *
   * @return
   */
  private rectf getmatrixrectf()
  {
    matrix matrix = mscalematrix;
    rectf rect = new rectf();
    drawable d = getdrawable();
    if (null != d)
    {
      rect.set(0, 0, d.getintrinsicwidth(), d.getintrinsicheight());
      matrix.maprect(rect);
    }
    return rect;
  }

在onscale里面记得调用:

?
1
2
3
4
5
6
7
/**
       * 设置缩放比例
       */
      mscalematrix.postscale(scalefactor, scalefactor,
          detector.getfocusx(), detector.getfocusy());
      checkborderandcenterwhenscale();
      setimagematrix(mscalematrix);

这样就好了,可以*的放大任何地方,并且不会出现边界出现白边,也能很好的让图片显示在屏幕中间(当图片宽或高小于屏幕);
3、贴下布局文件

?
1
2
3
4
5
6
7
8
9
10
11
12
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >
 
  <com.zhy.view.zoomimageview
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:scaletype="matrix"
    android:src="@drawable/xx" />
 
</relativelayout>

眼睛是心灵的窗户,咱们来放大看看,效果图:

Android实现手势滑动多点触摸缩放平移图片效果

好了,到此我们的图片随意的方法缩小~~~已经完成了~~~如果只需要缩放功能的,就可以拿去用了~
由于篇幅原因,下一篇将继续完善此view:
1、增加多点触控时移动
2、增加双击变大,双击变小
3、与viewpager一起使用时的事件冲突问题

以上就是本文的全部内容,希望对大家学习android软件编程有所帮助。