自定义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》