1、背景
现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位~~~
我相信看图的整个步骤,大家或者说用户应该不希望被打断把~~~“我擦,竟然不能放大,什么玩意,卸了~~“ , "我擦,竟然不能移动,留有何用,卸了~~"。
哈~所以对于图片的预览,一来,我们要让用户爽;二来,我们作为开发者,也得知道如何实现~~~
2、概述
想要做到图片支持多点触控,自由的进行缩放、平移,需要了解几个知识点:Matrix , GestureDetector , ScaleGestureDetector 以及事件分发机制,ps:不会咋办,不会你懂的。
1、Matrix
矩阵,看深入了都是3维矩阵的乘啊什么的,怪麻烦的~~
其实这么了解下就行了:
Matrix
数据结构:3维矩阵;
内部存储:new Float[9] ; 内部就是个一维数组,内部9个元素;可以进行setValues(float[] values)进行初始化
每个元素代表的意思:
1. {
2. MSCALE_X, MSKEW_X, MTRANS_X,
3. MSKEW_Y, MSCALE_Y, MTRANS_Y,
4. MPERSP_0, MPERSP_1, MPERSP_2
5. };
字面上,应该能看出来哪个代表x方向缩放,哪个代表垂直方向的偏移量吧~~有不认识的3个,没事,请无视。
操作
比如你想要设置matrix的偏移量为200,100
你可以这么写:
1. Matrix transMatrix = new Matrix();
2. float[] values = new float[] { 1.0, 0, 200, 0, 1.0, 100, 0, 0, 1.0 };
3. transMatrix.setValues(values);
如果需要在旋转30度,放大两倍~~
这么写其实怪麻烦的~~
Matrix提供了一些常用的API:例如我们可以这么写:
1. Matrix transMatrix = new Matrix();
2. 200, 100);
如何获取值:
当然了,我们对一个Matrix进行了各种操作,一会postScale,一会postTranslate;那么现在如何获得当前的缩放比例:
前面说setValues可以初始化,那么getValues就能拿到当前矩阵的值,拿到的是个一维数组,9个元素;再通过下标取对应值就可以。
比如我想知道现在x方向缩放比例:
1. public final float getScale()
2. {
3. scaleMatrix.getValues(matrixValues);
4. return matrixValues[Matrix.MSCALE_X];
5. }
好了,知道这些就够了~~
2、GestureDetector
嗯,自己看API,能够捕捉到长按、双击什么的;用法会在例子中
3、ScaleGestureDetector
嗯,有点像继承来的,其实不是的,独立的一个类~用于检测缩放的手势~~~用法会在例子中
3、实战
为了大家更好的理解,我会独立出每个功能,最后再整合到一起~~也方面大家对每个API的使用的学习。
1、自由的缩放
需求:当图片加载时,将图片在屏幕中居中;图片宽或高大于屏幕的,缩小至屏幕大小;自由对图片进行方法或缩小;
代码不是很长,直接贴代码了:
1. package com.zhy.view;
2.
3. import android.content.Context;
4. import android.graphics.Matrix;
5. import android.graphics.drawable.Drawable;
6. import android.util.AttributeSet;
7. import android.util.Log;
8. import android.view.MotionEvent;
9. import android.view.ScaleGestureDetector;
10. import android.view.ScaleGestureDetector.OnScaleGestureListener;
11. import android.view.View;
12. import android.view.View.OnTouchListener;
13. import android.view.ViewTreeObserver;
14. import android.widget.ImageView;
15.
16. public class ZoomImageView extends ImageView implements OnScaleGestureListener,
17. OnTouchListener, ViewTreeObserver.OnGlobalLayoutListener
18.
19. {
20. private static final String TAG = ZoomImageView.class.getSimpleName();
21.
22. public static final float SCALE_MAX = 4.0f;
23. /**
24. * 初始化时的缩放比例,如果图片宽或高大于屏幕,此值将小于0
25. */
26. private float initScale = 1.0f;
27.
28. /**
29. * 用于存放矩阵的9个值
30. */
31. private final float[] matrixValues = new float[9];
32.
33. private boolean once = true;
34.
35. /**
36. * 缩放的手势检测
37. */
38. private ScaleGestureDetector mScaleGestureDetector = null;
39.
40. private final Matrix mScaleMatrix = new Matrix();
41.
42. public ZoomImageView(Context context)
43. {
44. this(context, null);
45. }
46.
47. public ZoomImageView(Context context, AttributeSet attrs)
48. {
49. super(context, attrs);
50. super.setScaleType(ScaleType.MATRIX);
51. new ScaleGestureDetector(context, this);
52. this.setOnTouchListener(this);
53. }
54.
55. @Override
56. public boolean onScale(ScaleGestureDetector detector)
57. {
58. float scale = getScale();
59. float scaleFactor = detector.getScaleFactor();
60.
61. if (getDrawable() == null)
62. return true;
63.
64. /**
65. * 缩放的范围控制
66. */
67. if ((scale < SCALE_MAX && scaleFactor > 1.0f)
68. 1.0f))
69. {
70. /**
71. * 最大值最小值判断
72. */
73. if (scaleFactor * scale < initScale)
74. {
75. scaleFactor = initScale / scale;
76. }
77. if (scaleFactor * scale > SCALE_MAX)
78. {
79. scaleFactor = SCALE_MAX / scale;
80. }
81. /**
82. * 设置缩放比例
83. */
84. 2,
85. 2);
86. setImageMatrix(mScaleMatrix);
87. }
88. return true;
89.
90. }
91.
92. @Override
93. public boolean onScaleBegin(ScaleGestureDetector detector)
94. {
95. return true;
96. }
97.
98. @Override
99. public void onScaleEnd(ScaleGestureDetector detector)
100. {
101. }
102.
103. @Override
104. public boolean onTouch(View v, MotionEvent event)
105. {
106. return mScaleGestureDetector.onTouchEvent(event);
107.
108. }
109.
110.
111. /**
112. * 获得当前的缩放比例
113. *
114. * @return
115. */
116. public final float getScale()
117. {
118. mScaleMatrix.getValues(matrixValues);
119. return matrixValues[Matrix.MSCALE_X];
120. }
121.
122. @Override
123. protected void onAttachedToWindow()
124. {
125. super.onAttachedToWindow();
126. this);
127. }
128.
129. @SuppressWarnings("deprecation")
130. @Override
131. protected void onDetachedFromWindow()
132. {
133. super.onDetachedFromWindow();
134. this);
135. }
136.
137. @Override
138. public void onGlobalLayout()
139. {
140. if (once)
141. {
142. Drawable d = getDrawable();
143. if (d == null)
144. return;
145. " , " + d.getIntrinsicHeight());
146. int width = getWidth();
147. int height = getHeight();
148. // 拿到图片的宽和高
149. int dw = d.getIntrinsicWidth();
150. int dh = d.getIntrinsicHeight();
151. float scale = 1.0f;
152. // 如果图片的宽或者高大于屏幕,则缩放至屏幕的宽或者高
153. if (dw > width && dh <= height)
154. {
155. 1.0f / dw;
156. }
157. if (dh > height && dw <= width)
158. {
159. 1.0f / dh;
160. }
161. // 如果宽和高都大于屏幕,则让其按按比例适应屏幕大小
162. if (dw > width && dh > height)
163. {
164. 1.0f / width, dh * 1.0f / height);
165. }
166. initScale = scale;
167. // 图片移动至屏幕中心
168. 2, (height - dh) / 2);
169. mScaleMatrix
170. 2, getHeight() / 2);
171. setImageMatrix(mScaleMatrix);
172. false;
173. }
174.
175. }
176.
177. }
1、我们在onGlobalLayout的回调中,根据图片的宽和高以及屏幕的宽和高,对图片进行缩放以及移动至屏幕的中心。如果图片很小,那就正常显示,不放大了~
2、我们让OnTouchListener的MotionEvent交给ScaleGestureDetector进行处理
1. @Override
2. public boolean onTouch(View v, MotionEvent event)
3. {
4. return mScaleGestureDetector.onTouchEvent(event);
5.
6. }
3、在onScale的回调中对图片进行缩放的控制,首先进行缩放范围的判断,然后设置mScaleMatrix的scale值
现在的效果:
1、小于屏幕的宽和高
2、大于屏幕的宽和高
真机录的效果不太好~~凑合看~
现在已经能够~~~随意的放大缩小了~~~
源码点击下载
可是,可是,存在问题:
1、缩放的中心点,我们设置是固定的,屏幕中间
2、放大后,无法移动~
下面,我们先解决缩放的中心点问题,不能一直按屏幕中心么,像我这样的,我比较关注妹子的眼睛,我要放大那一块~~~
2、设置缩放中心
1、单纯的设置缩放中心
仅仅是设置中心很简单,直接修改下中心点 :
1. /**
2. * 设置缩放比例
3. */
4. mScaleMatrix.postScale(scaleFactor, scaleFactor,
5. detector.getFocusX(), detector.getFocusX());
6. setImageMatrix(mScaleMatrix);
但是,随意的中心点放大、缩小,会导致图片的位置的变化,最终导致,图片宽高大于屏幕时,图片与屏幕间出现白边;图片小于屏幕,但是不居中。
2、控制缩放时图片显示的范围
所以我们在缩放的时候需要手动控制下范围:
1. /**
2. * 在缩放时,进行图片显示范围的控制
3. */
4. private void checkBorderAndCenterWhenScale()
5. {
6.
7. RectF rect = getMatrixRectF();
8. float deltaX = 0;
9. float deltaY = 0;
10.
11. int width = getWidth();
12. int height = getHeight();
13.
14. // 如果宽或高大于屏幕,则控制范围
15. if (rect.width() >= width)
16. {
17. if (rect.left > 0)
18. {
19. deltaX = -rect.left;
20. }
21. if (rect.right < width)
22. {
23. deltaX = width - rect.right;
24. }
25. }
26. if (rect.height() >= height)
27. {
28. if (rect.top > 0)
29. {
30. deltaY = -rect.top;
31. }
32. if (rect.bottom < height)
33. {
34. deltaY = height - rect.bottom;
35. }
36. }
37. // 如果宽或高小于屏幕,则让其居中
38. if (rect.width() < width)
39. {
40. 0.5f - rect.right + 0.5f * rect.width();
41. }
42. if (rect.height() < height)
43. {
44. 0.5f - rect.bottom + 0.5f * rect.height();
45. }
46. "deltaX = " + deltaX + " , deltaY = " + deltaY);
47.
48. mScaleMatrix.postTranslate(deltaX, deltaY);
49.
50. }
51.
52. /**
53. * 根据当前图片的Matrix获得图片的范围
54. *
55. * @return
56. */
57. private RectF getMatrixRectF()
58. {
59. Matrix matrix = mScaleMatrix;
60. new RectF();
61. Drawable d = getDrawable();
62. if (null != d)
63. {
64. 0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
65. matrix.mapRect(rect);
66. }
67. return rect;
68. }
在onScale里面记得调用:
1. /**
2. * 设置缩放比例
3. */
4. mScaleMatrix.postScale(scaleFactor, scaleFactor,
5. detector.getFocusX(), detector.getFocusY());
6. checkBorderAndCenterWhenScale();
7. setImageMatrix(mScaleMatrix);
这样就好了,可以自由的放大任何地方,并且不会出现边界出现白边,也能很好的让图片显示在屏幕中间(当图片宽或高小于屏幕);
3、贴下布局文件
1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
2. xmlns:tools="http://schemas.android.com/tools"
3. android:layout_width="match_parent"
4. android:layout_height="match_parent" >
5.
6. <com.zhy.view.ZoomImageView
7. android:layout_width="fill_parent"
8. android:layout_height="fill_parent"
9. android:scaleType="matrix"
10. android:src="@drawable/xx" />
11.
12. </RelativeLayout>
眼睛是心灵的窗户,咱们来放大看看,效果图:
好了,到此我们的图片随意的方法缩小~~~已经完成了~~~如果只需要缩放功能的,就可以拿去用了~
由于篇幅原因,下一篇将继续完善此View:
1、增加多点触控时移动
2、增加双击变大,双击变小
3、与ViewPager一起使用时的事件冲突问题
以上是Android 大神张鸿洋写的,我还想再添加一些其他的内容
1.设置setImageMatrix(mScaleMatrix); 之前,需要设置super.setScaleType(ScaleType.MATRIX); 用来设置image如何匹配ImageView
2. 获取控件的宽高 ,再控件没有创建前是不能获得控件的宽高,具体查看如果获取控件宽高这篇博客
3. 对视图加载监听,和移除监听
@Override
protected void onAttachedToWindow()
{
super.onAttachedToWindow();
getViewTreeObserver(). //返回这个视图的层次
//Register a callback to be invoked when the global layout state or the visibility of views within the view tree changes
// 注册一个回调函数,当全局布局状态或视图树视图中的可见性改变时调用。
addOnGlobalLayoutListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
4.获取ImageView 的Matrix
scaleMatrix.getValues(matrixValue);
return matrixValue[Matrix.MSCALE_X];