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);

android yuv 缩放 安卓缩放功能_android yuv 缩放


如何获取值

当然了,我们对一个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、小于屏幕的宽和高


android yuv 缩放 安卓缩放功能_图形_02

2、大于屏幕的宽和高


android yuv 缩放 安卓缩放功能_图形_03

真机录的效果不太好~~凑合看~

现在已经能够~~~随意的放大缩小了~~~

源码点击下载

可是,可是,存在问题:

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>



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


android yuv 缩放 安卓缩放功能_android_04

好了,到此我们的图片随意的方法缩小~~~已经完成了~~~如果只需要缩放功能的,就可以拿去用了~

由于篇幅原因,下一篇将继续完善此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];