(四)双击放大与缩小图片

      自定义ZoomImageView实现到这里,基本上完成一大半了。在上一篇又给它添加了自由移动的功能。如果你没读过,可以点击下面的链接:
http://www.cnblogs.com/fuly550871915/p/4940103.html

      在这篇文章中,就来实现双击放大或者缩小图片。用到的知识点就是GestureDetector,用它来监测双击事件。至于双击后怎么缩放图片,相信在前面几篇文章中,你都已经很熟悉了。但是难点是,我们要求双击后缓慢的放大或者缩小,而不是一下子就放大到或者缩小到目标值。这里就要结合线程来处理了。其实处理的逻辑也很简单:比如说放大,我们每隔一段时间,就对图片进行放大一次,然后看看是不是达到要求的放大比例了,如果达到了就终止,否则继续放大,直到达到要求为止。

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

       红色部分是新加的代码。在第438行,首先要将触摸事件传递给mGestureDetector。然后在第105行开始监听双击事件。因为双击后将缩放比例直接设置给图片,那么图片会猛然放大,不是很好的用户体验。因此在这里,采用了post一个Runnable方式,不断的去调整缩放比例,直到达到要求为止。具体的逻辑代码中很详细了,仔细看看。大体逻辑很简单。注意在这里,我们将双击的缩放临界点设置为midScale,即如果当前缩放比例比它小,那么久放大到midScale,否则就缩小到initScale。

      然后运行下程序,效果如下:

 

      实现的效果还不错吧。截止到目前,自定义的ZoomImageView已经实现了自由缩放,自由移动以及双击放大和缩小。功能齐全,可以使用了。在一篇文章中,将用它和ViewPager结合使用:《(五)多点触控兼容ViewPager》

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