(五)多点触控之兼容ViewPager

时间:2021-04-01 16:37:05

      在上一篇文章中,自定义的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不要拦截触摸事件即可。然后我们运行程序,效果如下图:
(五)多点触控之兼容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);
}

      注意红色代码是核心。