在上一篇文章中,自定义的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); }
注意红色代码是核心。