(五)多点触控之兼容ViewPager

      在上一篇文章中,自定义的ZoomImageView已经实现了自由缩放,自由移动以及双击放大与缩小的功能。已经可以投入使用这个控件了。下面我们就在ViewPager中使用这个控件。如果你还没读过上一篇文章,可以点击下面的链接:

http://www.cnblogs.com/fuly550871915/p/4940193.html

一、在ViewPager中使用自定义的ZoomImageView

     快速的代建起ViewPager吧。修改activity_main.xml中的代码,如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
   >
   
     <android.support.v4.view.ViewPager
       android:id="@+id/id_viewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
         >
         
         
     </android.support.v4.view.ViewPager>

</RelativeLayout>

     然后修改MainActivity中的代码,如下:

 1 package com.example.zoom;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 
 6 import com.example.view.ZoomImageView;
 7 
 8 import android.os.Bundle;
 9 import android.support.v4.view.PagerAdapter;
10 import android.support.v4.view.ViewPager;
11 import android.view.View;
12 import android.view.ViewGroup;
13 import android.widget.ImageView;
14 import android.app.Activity;
15 
16 public class MainActivity extends Activity {
17     
18     
19     private ViewPager mViewPager;
20     private int[] imgIds = new int[]{R.drawable.mingxing0403,R.drawable.qw,
21             R.drawable.ic_launcher};
22     
23     private List<ImageView> mImageViews =new ArrayList<ImageView>();
24 
25   
26     protected void onCreate(Bundle savedInstanceState) {
27         super.onCreate(savedInstanceState);
28         setContentView(R.layout.activity_main);
29         
30         mViewPager = (ViewPager) findViewById(R.id.id_viewpager);
31         
32         for(int i=0;i<imgIds.length;i++)
33         {
34             ZoomImageView ziv = new ZoomImageView(getApplicationContext());
35             ziv.setImageResource(imgIds[i]);
36             mImageViews.add(ziv);
37         }
38         
39         
40         mViewPager.setAdapter(new PagerAdapter() {
41             
42     
43             
44             public boolean isViewFromObject(View arg0, Object arg1) {
45 
46                 return arg0 == arg1;
47             }
48             
49     
50             public int getCount() {
51         
52                 return mImageViews.size();
53             }
54 
55 
56 
57         
58             public void destroyItem(ViewGroup container, int position,
59                     Object object) {
60         
61                 container.removeView(mImageViews.get(position));
62             }
63 
64 
65             public Object instantiateItem(ViewGroup container, int position) {
66                 container.addView(mImageViews.get(position));
67                 return mImageViews.get(position);
68             }
69             
70             
71         });
72     }
73  
74 }

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

      红色代码是我们添加的。在这里,只需要请求父类ViewPager不要拦截触摸事件即可。然后我们运行程序,效果如下图:

     依然使用真机测试的,效果完全符合我们的预期。至此,本项目完结了。一个支持多点触控的ImageView做了出来。

二、小结

      在拖动图片的时候会与ViewPager发生冲突,因为ViewPager也会处理拖动事件。因此为了解决这个冲突,必须在ZoomImageView中添加代码:

    if(rectf.width()>getWidth()+0.01||rectf.height()>getHeight()+0.01)
            {
                
                //请求父类不要拦截ACTION_DOWN事件
                if(getParent() instanceof ViewPager)
                this.getParent().requestDisallowInterceptTouchEvent(true);
            }
            

      注意红色代码是核心。

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