(三)多点触控之自由移动缩放后的图片

      在上一篇文章中,将图片的自由缩放功能基本上完成了。效果还不错。如果你还没读过,可以点击下面的链接:
http://www.cnblogs.com/fuly550871915/p/4939954.html

      接下来这个项目要往前走,在自由缩放的基础上实现自由移动。要用的知识点就是OnTouchListener对移动手势的监控。在写代码之前我们应该考虑下面的几个问题:

(1)什么时候可以移动?
当图片比屏幕大时才需要移动,如果图片在屏幕内显示,没必要移动。
(2)当移动的距离达到多少时才触发移动效果?
在这里android提供了一个系统的临界值,直接使用即可。
(3)怎么使图片移动?
得到移动距离,利用Matrix.postTeanslate方法即可。

      这些问题都想清楚了,实现的逻辑就不难了。

      下面修改ZoomImageView,其中的代码为:

  1 package com.example.view;
  2 
  3 import android.annotation.SuppressLint;
  4 import android.content.Context;
  5 import android.graphics.Matrix;
  6 import android.graphics.RectF;
  7 import android.graphics.drawable.Drawable;
  8 import android.util.AttributeSet;
  9 import android.util.Log;
 10 import android.view.MotionEvent;
 11 import android.view.ScaleGestureDetector;
 12 import android.view.ScaleGestureDetector.OnScaleGestureListener;
 13 import android.view.View;
 14 import android.view.ViewConfiguration;
 15 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 16 import android.view.View.OnTouchListener;
 17 import android.widget.ImageView;
 18 
 19 public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, 
 20 OnScaleGestureListener, OnTouchListener
 21 {
 22     private boolean mOnce = false;//是否执行了一次
 23     
 24     /**
 25      * 初始缩放的比例
 26      */
 27     private float initScale;
 28     /**
 29      * 缩放比例
 30      */
 31     private float midScale;
 32     /**
 33      * 可放大的最大比例
 34      */
 35     private float maxScale;
 36     /**
 37      * 缩放矩阵
 38      */
 39     private Matrix scaleMatrix;
 40     
 41     /**
 42      * 缩放的手势监控类
 43      */
 44     private ScaleGestureDetector mScaleGestureDetector;
 45     
 46     //==========================下面是自由移动的成员变量======================================
 47     /**
 48      * 上一次移动的手指个数,也可以说是多点个数
 49      */
 50     private int mLastPoint;
 51     /**
 52      * 上次的中心点的x位置
 53      */
 54     private float mLastX;
 55     /**
 56      * 上一次中心点的y位置
 57      */
 58     private float mLastY;
 59     /**
 60      * 一个临界值,即是否触发移动的临界值
 61      */
 62     private float mScaleSlop;
 63     /**
 64      * 是否可移动
 65      */
 66     private boolean isCanDrag = false;
 67     
 68     
 69 
 70     public ZoomImageView(Context context)
 71     {
 72         this(context,null);
 73     }
 74     public ZoomImageView(Context context, AttributeSet attrs) 
 75     {
 76         this(context, attrs,0);
 77 
 78     }
 79     public ZoomImageView(Context context, AttributeSet attrs, int defStyle)
 80     {
 81         super(context, attrs, defStyle);
 82         
 83         scaleMatrix = new Matrix();
 84         
 85         setScaleType(ScaleType.MATRIX);
 86         
 87         mScaleGestureDetector = new ScaleGestureDetector(context, this);
 88         //触摸回调
 89         setOnTouchListener(this);
 90         //获得系统给定的触发移动效果的临界值
 91         mScaleSlop = ViewConfiguration.get(context).getScaledTouchSlop();
 92     }
 93     
 94     /**
 95      * 该方法在view与window绑定时被调用,且只会被调用一次,其在view的onDraw方法之前调用
 96      */
 97     protected void onAttachedToWindow()
 98     {
 99         super.onAttachedToWindow();
100         //注册监听器
101         getViewTreeObserver().addOnGlobalLayoutListener(this);
102     }
103     
104     /**
105      * 该方法在view被销毁时被调用
106      */
107     @SuppressLint("NewApi") protected void onDetachedFromWindow() 
108     {
109         super.onDetachedFromWindow();
110         //取消监听器
111         getViewTreeObserver().removeOnGlobalLayoutListener(this);
112     }
113     
114     /**
115      * 当一个view的布局加载完成或者布局发生改变时,OnGlobalLayoutListener会监听到,调用该方法
116      * 因此该方法可能会被多次调用,需要在合适的地方注册和取消监听器
117      */
118     public void onGlobalLayout() 
119     {
120         if(!mOnce)
121         {
122             //获得当前view的Drawable
123             Drawable d = getDrawable();
124             
125             if(d == null)
126             {
127                 return;
128             }
129             
130             //获得Drawable的宽和高
131             int dw = d.getIntrinsicWidth();
132             int dh = d.getIntrinsicHeight();
133             
134             //获取当前view的宽和高
135             int width = getWidth();
136             int height = getHeight();
137             
138             //缩放的比例,scale可能是缩小的比例也可能是放大的比例,看它的值是大于1还是小于1
139             float scale = 1.0f;
140             
141             //如果仅仅是图片宽度比view宽度大,则应该将图片按宽度缩小
142             if(dw>width&&dh<height)
143             {
144                 scale = width*1.0f/dw;
145             }
146             //如果图片和高度都比view的大,则应该按最小的比例缩小图片
147             if(dw>width&&dh>height)
148             {
149                 scale = Math.min(width*1.0f/dw, height*1.0f/dh);
150             }
151             //如果图片宽度和高度都比view的要小,则应该按最小的比例放大图片
152             if(dw<width&&dh<height)
153             {
154                 scale = Math.min(width*1.0f/dw, height*1.0f/dh);
155             }
156             //如果仅仅是高度比view的大,则按照高度缩小图片即可
157             if(dw<width&&dh>height)
158             {
159                 scale = height*1.0f/dh;
160             }
161             
162             //初始化缩放的比例
163             initScale = scale;
164             midScale = initScale*2;
165             maxScale = initScale*4;
166             
167             //移动图片到达view的中心
168             int dx = width/2 - dw/2;
169             int dy = height/2 - dh/2;
170             scaleMatrix.postTranslate(dx, dy);
171             
172             //缩放图片
173             scaleMatrix.postScale(initScale, initScale, width/2, height/2);    
174             
175             setImageMatrix(scaleMatrix);
176             mOnce = true;
177         }
178         
179     }
180     /**
181      * 获取当前已经缩放的比例
182      * @return  因为x方向和y方向比例相同,所以只返回x方向的缩放比例即可
183      */
184     private float getDrawableScale()
185     {
186         
187         float[] values = new float[9];
188         scaleMatrix.getValues(values);
189         
190         return values[Matrix.MSCALE_X];
191         
192     }
193 
194     /**
195      * 缩放手势进行时调用该方法
196      * 
197      * 缩放范围:initScale~maxScale
198      */
199     public boolean onScale(ScaleGestureDetector detector)
200     {
201         
202         if(getDrawable() == null)
203         {
204             return true;//如果没有图片,下面的代码没有必要运行
205         }
206         
207         float scale = getDrawableScale();
208         //获取当前缩放因子
209         float scaleFactor = detector.getScaleFactor();
210         
211         if((scale<maxScale&&scaleFactor>1.0f)||(scale>initScale&&scaleFactor<1.0f))
212         {
213             //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
214             if(scale*scaleFactor<initScale&&scaleFactor<1.0f)
215             {
216                 scaleFactor = initScale/scale;
217             }
218             //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子
219             if(scale*scaleFactor>maxScale&&scaleFactor>1.0f)
220             {
221                 scaleFactor = maxScale/scale;
222             }
223             
224 //            scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth()/2, getHeight()/2);
225             scaleMatrix.postScale(scaleFactor, scaleFactor,detector.getFocusX(), 
226                     detector.getFocusY());
227             
228             checkBoderAndCenter();//处理缩放后图片边界与屏幕有间隙或者不居中的问题
229             
230             
231             setImageMatrix(scaleMatrix);//千万不要忘记设置这个,我总是忘记
232         }
233         
234         
235     
236         return true;
237     }
238     /**
239      * 处理缩放后图片边界与屏幕有间隙或者不居中的问题
240      */
241     private void checkBoderAndCenter()
242     {
243        RectF rectf = getDrawableRectF();
244        
245        int width = getWidth();
246        int height = getHeight();
247        
248        float delaX =0;
249        float delaY = 0;
250        
251        if(rectf.width()>=width)
252        {
253            if(rectf.left>0)
254            {
255              delaX = - rectf.left;  
256            }
257            
258            if(rectf.right<width)
259            {
260                delaX = width - rectf.right;
261            }   
262        }
263        
264        if(rectf.height()>=height)
265        {
266            if(rectf.top>0)
267            {
268                delaY = -rectf.top;
269            }
270            if(rectf.bottom<height)
271            {
272                delaY = height - rectf.bottom;
273            }
274        }
275        
276        if(rectf.width()<width)
277        {
278            delaX = width/2 - rectf.right+ rectf.width()/2;
279        }
280        
281        if(rectf.height()<height)
282        {
283            delaY =  height/2 - rectf.bottom+ rectf.height()/2;
284        }
285        
286        scaleMatrix.postTranslate(delaX, delaY);
287     }
288     /**
289      * 获取图片根据矩阵变换后的四个角的坐标,即left,top,right,bottom
290      * @return 
291      */
292     private RectF getDrawableRectF()
293     {
294         Matrix matrix = scaleMatrix;
295         RectF rectf = new RectF();
296         Drawable d = getDrawable();
297         if(d != null)
298         {
299             
300             rectf.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
301         }
302         
303         matrix.mapRect(rectf);
304         return  rectf;
305     }
306     /**
307      * 缩放手势开始时调用该方法
308      */
309     public boolean onScaleBegin(ScaleGestureDetector detector) 
310     {    
311         //返回为true,则缩放手势事件往下进行,否则到此为止,即不会执行onScale和onScaleEnd方法
312         return true;
313     }
314     /**
315      * 缩放手势完成后调用该方法
316      */
317     public void onScaleEnd(ScaleGestureDetector detector)
318     {
319         
320         
321     }
322 
323     /**
324      * 监听触摸事件
325      */
326     public boolean onTouch(View v, MotionEvent event)
327     {
328     
329         if(mScaleGestureDetector != null)
330         {
331             //将触摸事件传递给手势缩放这个类
332             mScaleGestureDetector.onTouchEvent(event);
333         }
334         
335         
336         //获得多点个数,也叫屏幕上手指的个数
337         int pointCount = event.getPointerCount();
338         
339         float x =0;
340         float y =0;//中心点的x和y
341         
342         for(int i=0;i<pointCount;i++)
343         {
344             x+=event.getX(i);
345             y+=event.getY(i);
346         }
347         
348         //求出中心点的位置
349         x/= pointCount;
350         y/= pointCount;
351         
352         //如果手指的数量发生了改变,则不移动
353         if(mLastPoint != pointCount)
354         {
355             isCanDrag = false;
356             mLastX = x;
357             mLastY = y;
358             
359         }
360         mLastPoint = pointCount;
361         
362         
363         switch(event.getAction())
364         {
365         case MotionEvent.ACTION_MOVE:
366             
367             //求出移动的距离
368             float dx = x - mLastX;
369             float dy = y- mLastY;
370             
371             if(!isCanDrag)
372             {
373                 isCanDrag = isCanDrag(dx,dy);
374             }
375             
376             if(isCanDrag)
377             {
378                 //如果图片能正常显示,就不需要移动了
379                 RectF rectf = getDrawableRectF();
380                 if(rectf.width()<=getWidth())
381                 {
382                     dx = 0;
383                 }
384                 if(rectf.height()<=getHeight())
385                 {
386                     dy = 0;
387                 }
388                 
389                 
390                 //开始移动
391                 scaleMatrix.postTranslate(dx, dy);
392                 //处理移动后图片边界与屏幕有间隙或者不居中的问题
393                 checkBoderAndCenterWhenMove();
394                 setImageMatrix(scaleMatrix);
395             }
396             
397             mLastX = x;
398             mLastY = y;
399             
400             
401             break;
402         case MotionEvent.ACTION_UP:
403         case MotionEvent.ACTION_CANCEL: 
404                     mLastPoint = 0;    
405             break;
406         
407         }
408 
409         return true;
410     }
411     /**
412      * 处理移动后图片边界与屏幕有间隙或者不居中的问题
413      * 这跟我们前面写的代码很像
414      */
415     private void checkBoderAndCenterWhenMove() {
416         
417         RectF rectf = getDrawableRectF();
418         
419         float delaX = 0;
420         float delaY = 0;
421         int width = getWidth();
422         int height = getHeight();
423         
424         if(rectf.width()>width&&rectf.left>0)
425         {
426             delaX = - rectf.left;
427         }
428         if(rectf.width()>width&&rectf.right<width)
429         {
430             delaX = width - rectf.right;
431         }
432         if(rectf.height()>height&&rectf.top>0)
433         {
434             delaY = - rectf.top;
435         }
436         if(rectf.height()>height&&rectf.bottom<height)
437         {
438             delaY = height - rectf.bottom;
439         }
440         
441         scaleMatrix.postTranslate(delaX, delaY);
442     }
443     /**
444      * 判断是否触发移动效果
445      * @param dx
446      * @param dy
447      * @return  
448      */
449     private boolean isCanDrag(float dx, float dy) {
450         
451         return Math.sqrt(dx*dx+dy*dy)>mScaleSlop;
452     }
453 
454     
455     
456 
457 }

       红色部分就是增加的代码了。在这里简单解释一下。首先拿到系统给定的判定是否触发移动效果的临界值,即91行获得mScaleSlop,然后在isCanDrag方法中根据移动的距离,判定是否足够触发移动效果。在onTouch方法中,编写具体的移动逻辑。由于是多点触控,即屏幕上可能不止一个手指。因此通过计算得到中心触控的位置x和y,即第349行和350行所做的事情。另外通过mLastX和mLastY保存之前移动的中心位置。通过计算当前中心位置与之前保存的中心位置的差值,就可以得到需要图片移动的距离了,即第368和第369行代码所做的事情。之后就可以判断是否触发移动了,如果触发了那就移动呗。移动完成后,再做些善后处理。大体逻辑就是这样子。很简单,代码也很详实。不再解释了。哦,对了,因为移动过程中,也可能会出现与屏幕间有空隙,因此需要checkBoderAndCenterWhenMove一下。这里面的代码跟之前写的的差不多了,就不多余解释了,原理一样。

      好了,运行程序吧,效果如下:

       效果达到了。是不是发现代码越写越少,越简单呢?因为这个项目快要完成了,基本没有什么大的逻辑了。下面就 快马加鞭,实现双击放大与缩小图片的功能吧:《(四)双击放大与缩小图片》

原文地址:https://www.cnblogs.com/fuly550871915/p/4940103.html