由于项目需求的原因,最近一直在研究可缩放性ImageView,用本文来记录一下最近所学:
该ImageView的实现功能有:
1)初步打开时,图片按比例满屏(填充ImageView)显示。
2)在放大缩小过程中,可以控制最大放大比例和最小缩小比例。
3)在缩放过程中,若图片的宽或高小于ImageView,则在图片在宽或高居中显示。
4)在放大后,可以移动图片,并且限制好移动的边界,不会超出图片。
5)实现双击放大或缩小的功能。(若当前图片显示为最大的比例则缩小为最小比例,若不是最小比例则放大了最大比例)
在讲代码之前,首先应该说说一个类,Matrix。因为我们在处理图片的过程中,需要图片的位移,缩放等等,而Matrix刚好就是帮我们封装好了这些数据,具体的,大家可以看看这篇文章:android 从matrix获取处理过的图片的实际宽度重点是了解Matrix里面数组的含义。
上代码,具体的说明代码都有一一介绍:
MyZoomImageView.java文件:
[java]
1. package com.xiaoyan.doubletouch;
2.
3. import android.content.Context;
4. import android.graphics.Matrix;
5. import android.graphics.PointF;
6. import android.graphics.drawable.Drawable;
7. import android.util.AttributeSet;
8. import android.util.FloatMath;
9. import android.view.MotionEvent;
10. import android.view.View;
11. import android.view.ViewTreeObserver;
12. import android.view.ViewTreeObserver.OnGlobalLayoutListener;
13. import android.widget.ImageView;
14.
15. /**
16. * 缩放ImageView
17. *
18. * @author xiejinxiong
19. *
20. */
21. public class MyZoomImageView extends ImageView {
22.
23. /** ImageView高度 */
24. private int imgHeight;
25. /** ImageView宽度 */
26. private int imgWidth;
27. /** 图片高度 */
28. private int intrinsicHeight;
29. /** 图片宽度 */
30. private int intrinsicWidth;
31. /** 最大缩放级别 */
32. private float mMaxScale = 2.0f;
33. /** 最小缩放级别 */
34. private float mMinScale = 0.2f;
35. /** 用于记录拖拉图片移动的坐标位置 */
36. private Matrix matrix = new Matrix();
37. /** 用于记录图片要进行拖拉时候的坐标位置 */
38. private Matrix currentMatrix = new Matrix();
39. /** 记录第一次点击的时间 */
40. private long firstTouchTime = 0;
41. /** 时间点击的间隔 */
42. private int intervalTime = 250;
43. /** 第一次点完坐标 */
44. private PointF firstPointF;
45.
46. public MyZoomImageView(Context context) {
47. super(context);
48. initUI();
49. }
50.
51. public MyZoomImageView(Context context, AttributeSet attrs) {
52. super(context, attrs);
53. initUI();
54. }
55.
56. public MyZoomImageView(Context context, AttributeSet attrs, int defStyle) {
57. super(context, attrs, defStyle);
58. // TODO Auto-generated constructor stub
59. initUI();
60. }
61.
62. /**
63. * 初始化UI
64. */
65. private void initUI() {
66.
67. this.setScaleType(ScaleType.FIT_CENTER);
68. this.setOnTouchListener(new TouchListener());
69.
70. getImageViewWidthHeight();
71. getIntrinsicWidthHeight();
72. }
73.
74. /**
75. * 获得图片内在宽高
76. */
77. private void getIntrinsicWidthHeight() {
78. this.getDrawable();
79.
80. // 初始化bitmap的宽高
81. intrinsicHeight = drawable.getIntrinsicHeight();
82. intrinsicWidth = drawable.getIntrinsicWidth();
83. }
84.
85. private final class TouchListener implements OnTouchListener {
86.
87. /** 记录是拖拉照片模式还是放大缩小照片模式 */
88. private int mode = 0;// 初始状态
89. /** 拖拉照片模式 */
90. private static final int MODE_DRAG = 1;
91. /** 放大缩小照片模式 */
92. private static final int MODE_ZOOM = 2;
93. /** 用于记录开始时候的坐标位置 */
94. private PointF startPoint = new PointF();
95. /** 两个手指的开始距离 */
96. private float startDis;
97. /** 两个手指的中间点 */
98. private PointF midPoint;
99.
100. public boolean onTouch(View v, MotionEvent event) {
101.
102. /** 通过与运算保留最后八位 MotionEvent.ACTION_MASK = 255 */
103. switch (event.getAction() & MotionEvent.ACTION_MASK) {// 单点监听和多点触碰监听
104. // 手指压下屏幕
105. case MotionEvent.ACTION_DOWN:
106. mode = MODE_DRAG;
107. // 记录ImageView当前的移动位置
108. currentMatrix.set(getImageMatrix());
109. startPoint.set(event.getX(), event.getY());
110. matrix.set(currentMatrix);
111. makeImageViewFit();
112. break;
113. // 手指在屏幕上移动,改事件会被不断触发
114. case MotionEvent.ACTION_MOVE:
115. // 拖拉图片
116. if (mode == MODE_DRAG) {
117. // System.out.println("ACTION_MOVE_____MODE_DRAG");
118. float dx = event.getX() - startPoint.x; // 得到x轴的移动距离
119. float dy = event.getY() - startPoint.y; // 得到x轴的移动距离
120. // 在没有移动之前的位置上进行移动z
121. matrix.set(currentMatrix);
122. float[] values = new float[9];
123. matrix.getValues(values);
124. dx = checkDxBound(values, dx);
125. dy = checkDyBound(values, dy);
126. matrix.postTranslate(dx, dy);
127.
128. }
129. // 放大缩小图片
130. else if (mode == MODE_ZOOM) {
131. float endDis = distance(event);// 结束距离
132. if (endDis > 10f) { // 两个手指并拢在一起的时候像素大于10
133. float scale = endDis / startDis;// 得到缩放倍数
134. matrix.set(currentMatrix);
135.
136. float[] values = new float[9];
137. matrix.getValues(values);
138.
139. scale = checkFitScale(scale, values);
140.
141. matrix.postScale(scale, scale, midPoint.x, midPoint.y);
142.
143. }
144. }
145. break;
146. // 手指离开屏幕
147. case MotionEvent.ACTION_UP:
148. setDoubleTouchEvent(event);
149.
150. case MotionEvent.ACTION_POINTER_UP:
151. // System.out.println("ACTION_POINTER_UP");
152. 0;
153. // matrix.set(currentMatrix);
154. float[] values = new float[9];
155. matrix.getValues(values);
156. makeImgCenter(values);
157. break;
158. // 当屏幕上已经有触点(手指),再有一个触点压下屏幕
159. case MotionEvent.ACTION_POINTER_DOWN:
160. // System.out.println("ACTION_POINTER_DOWN");
161. mode = MODE_ZOOM;
162. /** 计算两个手指间的距离 */
163. startDis = distance(event);
164. /** 计算两个手指间的中间点 */
165. if (startDis > 10f) { // 两个手指并拢在一起的时候像素大于10
166. midPoint = mid(event);
167. // 记录当前ImageView的缩放倍数
168. currentMatrix.set(getImageMatrix());
169. }
170. break;
171. }
172. setImageMatrix(matrix);
173. return true;
174. }
175.
176. /** 计算两个手指间的距离 */
177. private float distance(MotionEvent event) {
178. float dx = event.getX(1) - event.getX(0);
179. float dy = event.getY(1) - event.getY(0);
180. /** 使用勾股定理返回两点之间的距离 */
181. return FloatMath.sqrt(dx * dx + dy * dy);
182. }
183.
184. /** 计算两个手指间的中间点 */
185. private PointF mid(MotionEvent event) {
186. float midX = (event.getX(1) + event.getX(0)) / 2;
187. float midY = (event.getY(1) + event.getY(0)) / 2;
188. return new PointF(midX, midY);
189. }
190.
191. /**
192. * 和当前矩阵对比,检验dy,使图像移动后不会超出ImageView边界
193. *
194. * @param values
195. * @param dy
196. * @return
197. */
198. private float checkDyBound(float[] values, float dy) {
199.
200. float height = imgHeight;
201. if (intrinsicHeight * values[Matrix.MSCALE_Y] < height)
202. return 0;
203. if (values[Matrix.MTRANS_Y] + dy > 0)
204. dy = -values[Matrix.MTRANS_Y];
205. else if (values[Matrix.MTRANS_Y] + dy < -(intrinsicHeight
206. * values[Matrix.MSCALE_Y] - height))
207. dy = -(intrinsicHeight * values[Matrix.MSCALE_Y] - height)
208. - values[Matrix.MTRANS_Y];
209. return dy;
210. }
211.
212. /**
213. * 和当前矩阵对比,检验dx,使图像移动后不会超出ImageView边界
214. *
215. * @param values
216. * @param dx
217. * @return
218. */
219. private float checkDxBound(float[] values, float dx) {
220.
221. float width = imgWidth;
222. if (intrinsicWidth * values[Matrix.MSCALE_X] < width)
223. return 0;
224. if (values[Matrix.MTRANS_X] + dx > 0)
225. dx = -values[Matrix.MTRANS_X];
226. else if (values[Matrix.MTRANS_X] + dx < -(intrinsicWidth
227. * values[Matrix.MSCALE_X] - width))
228. dx = -(intrinsicWidth * values[Matrix.MSCALE_X] - width)
229. - values[Matrix.MTRANS_X];
230. return dx;
231. }
232.
233. /**
234. * MSCALE用于处理缩放变换
235. *
236. *
237. * MSKEW用于处理错切变换
238. *
239. *
240. * MTRANS用于处理平移变换
241. */
242.
243. /**
244. * 检验scale,使图像缩放后不会超出最大倍数
245. *
246. * @param scale
247. * @param values
248. * @return
249. */
250. private float checkFitScale(float scale, float[] values) {
251. if (scale * values[Matrix.MSCALE_X] > mMaxScale)
252. scale = mMaxScale / values[Matrix.MSCALE_X];
253. if (scale * values[Matrix.MSCALE_X] < mMinScale)
254. scale = mMinScale / values[Matrix.MSCALE_X];
255. return scale;
256. }
257.
258. /**
259. * 促使图片居中
260. *
261. * @param values
262. * (包含着图片变化信息)
263. */
264. private void makeImgCenter(float[] values) {
265.
266. // 缩放后图片的宽高
267. float zoomY = intrinsicHeight * values[Matrix.MSCALE_Y];
268. float zoomX = intrinsicWidth * values[Matrix.MSCALE_X];
269. // 图片左上角Y坐标
270. float leftY = values[Matrix.MTRANS_Y];
271. // 图片左上角X坐标
272. float leftX = values[Matrix.MTRANS_X];
273. // 图片右下角Y坐标
274. float rightY = leftY + zoomY;
275. // 图片右下角X坐标
276. float rightX = leftX + zoomX;
277.
278. // 使图片垂直居中
279. if (zoomY < imgHeight) {
280. float marY = (imgHeight - zoomY) / 2.0f;
281. 0, marY - leftY);
282. }
283.
284. // 使图片水平居中
285. if (zoomX < imgWidth) {
286.
287. float marX = (imgWidth - zoomX) / 2.0f;
288. 0);
289.
290. }
291.
292. // 使图片缩放后上下不留白(即当缩放后图片的大小大于imageView的大小,但是上面或下面留出一点空白的话,将图片移动占满空白处)
293. if (zoomY >= imgHeight) {
294. if (leftY > 0) {// 判断图片上面留白
295. 0, -leftY);
296. }
297. if (rightY < imgHeight) {// 判断图片下面留白
298. 0, imgHeight - rightY);
299. }
300. }
301.
302. // 使图片缩放后左右不留白
303. if (zoomX >= imgWidth) {
304. if (leftX > 0) {// 判断图片左边留白
305. 0);
306. }
307. if (rightX < imgWidth) {// 判断图片右边不留白
308. 0);
309. }
310. }
311. }
312.
313. }
314.
315. /**
316. * 获取ImageView的宽高
317. */
318. private void getImageViewWidthHeight() {
319. ViewTreeObserver vto2 = getViewTreeObserver();
320. new OnGlobalLayoutListener() {
321. @SuppressWarnings("deprecation")
322. public void onGlobalLayout() {
323. this);
324. imgWidth = getWidth();
325. imgHeight = getHeight();
326.
327. }
328. });
329. }
330.
331. /**
332. * 使得ImageView一开始便显示最适合的宽高比例,便是刚好容下的样子
333. */
334. private void makeImageViewFit() {
335. if (getScaleType() != ScaleType.MATRIX) {
336. setScaleType(ScaleType.MATRIX);
337.
338. 1.0f, 1.0f, imgWidth / 2, imgHeight / 2);
339. }
340. }
341.
342. /**
343. * 双击事件触发
344. *
345. * @param values
346. */
347. private void setDoubleTouchEvent(MotionEvent event) {
348.
349. float values[] = new float[9];
350. matrix.getValues(values);
351. // 存储当前时间
352. long currentTime = System.currentTimeMillis();
353. // 判断两次点击间距时间是否符合
354. if (currentTime - firstTouchTime >= intervalTime) {
355. firstTouchTime = currentTime;
356. new PointF(event.getX(), event.getY());
357. else {
358. // 判断两次点击之间的距离是否小于30f
359. if (Math.abs(event.getX() - firstPointF.x) < 30f
360. && Math.abs(event.getY() - firstPointF.y) < 30f) {
361. // 判断当前缩放比例与最大最小的比例
362. if (values[Matrix.MSCALE_X] < mMaxScale) {
363. matrix.postScale(mMaxScale / values[Matrix.MSCALE_X],
364. mMaxScale / values[Matrix.MSCALE_X], event.getX(),
365. event.getY());
366. else {
367. matrix.postScale(mMinScale / values[Matrix.MSCALE_X],
368. mMinScale / values[Matrix.MSCALE_X], event.getX(),
369. event.getY());
370. }
371. }
372.
373. }
374. }
375.
376. /**
377. * 设置图片的最大和最小的缩放比例
378. *
379. * @param mMaxScale
380. * @param mMinScale
381. */
382. public void setPicZoomHeightWidth(float mMaxScale, float mMinScale) {
383. this.mMaxScale = mMaxScale;
384. this.mMinScale = mMinScale;
385. }
386.
387. }
xml文件:
[html]
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. tools:context="com.xiaoyan.doubletouch.MainActivity" >
6.
7. <com.xiaoyan.doubletouch.MyZoomImageView
8. android:id="@+id/imageView"
9. android:layout_width="fill_parent"
10. android:layout_height="fill_parent"
11. android:src="@drawable/a" />
12.
13. </RelativeLayout>
具体实现效果:
最初显示:
放大显示: