在上一篇文章中,将图片的自由缩放功能基本上完成了。效果还不错。如果你还没读过,可以点击下面的链接:
http://www.cnblogs.com/fuly550871915/p/4939954.html
接下来这个项目要往前走,在自由缩放的基础上实现自由移动。要用的知识点就是OnTouchListener对移动手势的监控。在写代码之前我们应该考虑下面的几个问题:
(1)什么时候可以移动?当图片比屏幕大时才需要移动,如果图片在屏幕内显示,没必要移动。(2)当移动的距离达到多少时才触发移动效果?在这里android提供了一个系统的临界值,直接使用即可。(3)怎么使图片移动?得到移动距离,利用Matrix.postTeanslate方法即可。
这些问题都想清楚了,实现的逻辑就不难了。
下面修改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.MotionEvent; 11 import android.view.ScaleGestureDetector; 12 import android.view.ScaleGestureDetector.OnScaleGestureListener; 13 import android.view.View; 14 import android.view.ViewConfiguration; 15 import android.view.ViewTreeObserver.OnGlobalLayoutListener; 16 import android.view.View.OnTouchListener; 17 import android.widget.ImageView; 18 19 public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, 20 OnScaleGestureListener, OnTouchListener 21 { 22 private boolean mOnce = false;//是否执行了一次 23 24 /** 25 * 初始缩放的比例 26 */ 27 private float initScale; 28 /** 29 * 缩放比例 30 */ 31 private float midScale; 32 /** 33 * 可放大的最大比例 34 */ 35 private float maxScale; 36 /** 37 * 缩放矩阵 38 */ 39 private Matrix scaleMatrix; 40 41 /** 42 * 缩放的手势监控类 43 */ 44 private ScaleGestureDetector mScaleGestureDetector; 45 46 //==========================下面是自由移动的成员变量====================================== 47 /** 48 * 上一次移动的手指个数,也可以说是多点个数 49 */ 50 private int mLastPoint; 51 /** 52 * 上次的中心点的x位置 53 */ 54 private float mLastX; 55 /** 56 * 上一次中心点的y位置 57 */ 58 private float mLastY; 59 /** 60 * 一个临界值,即是否触发移动的临界值 61 */ 62 private float mScaleSlop; 63 /** 64 * 是否可移动 65 */ 66 private boolean isCanDrag = false; 67 68 69 70 public ZoomImageView(Context context) 71 { 72 this(context,null); 73 } 74 public ZoomImageView(Context context, AttributeSet attrs) 75 { 76 this(context, attrs,0); 77 78 } 79 public ZoomImageView(Context context, AttributeSet attrs, int defStyle) 80 { 81 super(context, attrs, defStyle); 82 83 scaleMatrix = new Matrix(); 84 85 setScaleType(ScaleType.MATRIX); 86 87 mScaleGestureDetector = new ScaleGestureDetector(context, this); 88 //触摸回调 89 setOnTouchListener(this); 90 //获得系统给定的触发移动效果的临界值 91 mScaleSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 92 } 93 94 /** 95 * 该方法在view与window绑定时被调用,且只会被调用一次,其在view的onDraw方法之前调用 96 */ 97 protected void onAttachedToWindow() 98 { 99 super.onAttachedToWindow(); 100 //注册监听器 101 getViewTreeObserver().addOnGlobalLayoutListener(this); 102 } 103 104 /** 105 * 该方法在view被销毁时被调用 106 */ 107 @SuppressLint("NewApi") protected void onDetachedFromWindow() 108 { 109 super.onDetachedFromWindow(); 110 //取消监听器 111 getViewTreeObserver().removeOnGlobalLayoutListener(this); 112 } 113 114 /** 115 * 当一个view的布局加载完成或者布局发生改变时,OnGlobalLayoutListener会监听到,调用该方法 116 * 因此该方法可能会被多次调用,需要在合适的地方注册和取消监听器 117 */ 118 public void onGlobalLayout() 119 { 120 if(!mOnce) 121 { 122 //获得当前view的Drawable 123 Drawable d = getDrawable(); 124 125 if(d == null) 126 { 127 return; 128 } 129 130 //获得Drawable的宽和高 131 int dw = d.getIntrinsicWidth(); 132 int dh = d.getIntrinsicHeight(); 133 134 //获取当前view的宽和高 135 int width = getWidth(); 136 int height = getHeight(); 137 138 //缩放的比例,scale可能是缩小的比例也可能是放大的比例,看它的值是大于1还是小于1 139 float scale = 1.0f; 140 141 //如果仅仅是图片宽度比view宽度大,则应该将图片按宽度缩小 142 if(dw>width&&dh<height) 143 { 144 scale = width*1.0f/dw; 145 } 146 //如果图片和高度都比view的大,则应该按最小的比例缩小图片 147 if(dw>width&&dh>height) 148 { 149 scale = Math.min(width*1.0f/dw, height*1.0f/dh); 150 } 151 //如果图片宽度和高度都比view的要小,则应该按最小的比例放大图片 152 if(dw<width&&dh<height) 153 { 154 scale = Math.min(width*1.0f/dw, height*1.0f/dh); 155 } 156 //如果仅仅是高度比view的大,则按照高度缩小图片即可 157 if(dw<width&&dh>height) 158 { 159 scale = height*1.0f/dh; 160 } 161 162 //初始化缩放的比例 163 initScale = scale; 164 midScale = initScale*2; 165 maxScale = initScale*4; 166 167 //移动图片到达view的中心 168 int dx = width/2 - dw/2; 169 int dy = height/2 - dh/2; 170 scaleMatrix.postTranslate(dx, dy); 171 172 //缩放图片 173 scaleMatrix.postScale(initScale, initScale, width/2, height/2); 174 175 setImageMatrix(scaleMatrix); 176 mOnce = true; 177 } 178 179 } 180 /** 181 * 获取当前已经缩放的比例 182 * @return 因为x方向和y方向比例相同,所以只返回x方向的缩放比例即可 183 */ 184 private float getDrawableScale() 185 { 186 187 float[] values = new float[9]; 188 scaleMatrix.getValues(values); 189 190 return values[Matrix.MSCALE_X]; 191 192 } 193 194 /** 195 * 缩放手势进行时调用该方法 196 * 197 * 缩放范围:initScale~maxScale 198 */ 199 public boolean onScale(ScaleGestureDetector detector) 200 { 201 202 if(getDrawable() == null) 203 { 204 return true;//如果没有图片,下面的代码没有必要运行 205 } 206 207 float scale = getDrawableScale(); 208 //获取当前缩放因子 209 float scaleFactor = detector.getScaleFactor(); 210 211 if((scale<maxScale&&scaleFactor>1.0f)||(scale>initScale&&scaleFactor<1.0f)) 212 { 213 //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子 214 if(scale*scaleFactor<initScale&&scaleFactor<1.0f) 215 { 216 scaleFactor = initScale/scale; 217 } 218 //如果缩小的范围比允许的最小范围还要小,就重置缩放因子为当前的状态的因子 219 if(scale*scaleFactor>maxScale&&scaleFactor>1.0f) 220 { 221 scaleFactor = maxScale/scale; 222 } 223 224 // scaleMatrix.postScale(scaleFactor, scaleFactor, getWidth()/2, getHeight()/2); 225 scaleMatrix.postScale(scaleFactor, scaleFactor,detector.getFocusX(), 226 detector.getFocusY()); 227 228 checkBoderAndCenter();//处理缩放后图片边界与屏幕有间隙或者不居中的问题 229 230 231 setImageMatrix(scaleMatrix);//千万不要忘记设置这个,我总是忘记 232 } 233 234 235 236 return true; 237 } 238 /** 239 * 处理缩放后图片边界与屏幕有间隙或者不居中的问题 240 */ 241 private void checkBoderAndCenter() 242 { 243 RectF rectf = getDrawableRectF(); 244 245 int width = getWidth(); 246 int height = getHeight(); 247 248 float delaX =0; 249 float delaY = 0; 250 251 if(rectf.width()>=width) 252 { 253 if(rectf.left>0) 254 { 255 delaX = - rectf.left; 256 } 257 258 if(rectf.right<width) 259 { 260 delaX = width - rectf.right; 261 } 262 } 263 264 if(rectf.height()>=height) 265 { 266 if(rectf.top>0) 267 { 268 delaY = -rectf.top; 269 } 270 if(rectf.bottom<height) 271 { 272 delaY = height - rectf.bottom; 273 } 274 } 275 276 if(rectf.width()<width) 277 { 278 delaX = width/2 - rectf.right+ rectf.width()/2; 279 } 280 281 if(rectf.height()<height) 282 { 283 delaY = height/2 - rectf.bottom+ rectf.height()/2; 284 } 285 286 scaleMatrix.postTranslate(delaX, delaY); 287 } 288 /** 289 * 获取图片根据矩阵变换后的四个角的坐标,即left,top,right,bottom 290 * @return 291 */ 292 private RectF getDrawableRectF() 293 { 294 Matrix matrix = scaleMatrix; 295 RectF rectf = new RectF(); 296 Drawable d = getDrawable(); 297 if(d != null) 298 { 299 300 rectf.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 301 } 302 303 matrix.mapRect(rectf); 304 return rectf; 305 } 306 /** 307 * 缩放手势开始时调用该方法 308 */ 309 public boolean onScaleBegin(ScaleGestureDetector detector) 310 { 311 //返回为true,则缩放手势事件往下进行,否则到此为止,即不会执行onScale和onScaleEnd方法 312 return true; 313 } 314 /** 315 * 缩放手势完成后调用该方法 316 */ 317 public void onScaleEnd(ScaleGestureDetector detector) 318 { 319 320 321 } 322 323 /** 324 * 监听触摸事件 325 */ 326 public boolean onTouch(View v, MotionEvent event) 327 { 328 329 if(mScaleGestureDetector != null) 330 { 331 //将触摸事件传递给手势缩放这个类 332 mScaleGestureDetector.onTouchEvent(event); 333 } 334 335 336 //获得多点个数,也叫屏幕上手指的个数 337 int pointCount = event.getPointerCount(); 338 339 float x =0; 340 float y =0;//中心点的x和y 341 342 for(int i=0;i<pointCount;i++) 343 { 344 x+=event.getX(i); 345 y+=event.getY(i); 346 } 347 348 //求出中心点的位置 349 x/= pointCount; 350 y/= pointCount; 351 352 //如果手指的数量发生了改变,则不移动 353 if(mLastPoint != pointCount) 354 { 355 isCanDrag = false; 356 mLastX = x; 357 mLastY = y; 358 359 } 360 mLastPoint = pointCount; 361 362 363 switch(event.getAction()) 364 { 365 case MotionEvent.ACTION_MOVE: 366 367 //求出移动的距离 368 float dx = x - mLastX; 369 float dy = y- mLastY; 370 371 if(!isCanDrag) 372 { 373 isCanDrag = isCanDrag(dx,dy); 374 } 375 376 if(isCanDrag) 377 { 378 //如果图片能正常显示,就不需要移动了 379 RectF rectf = getDrawableRectF(); 380 if(rectf.width()<=getWidth()) 381 { 382 dx = 0; 383 } 384 if(rectf.height()<=getHeight()) 385 { 386 dy = 0; 387 } 388 389 390 //开始移动 391 scaleMatrix.postTranslate(dx, dy); 392 //处理移动后图片边界与屏幕有间隙或者不居中的问题 393 checkBoderAndCenterWhenMove(); 394 setImageMatrix(scaleMatrix); 395 } 396 397 mLastX = x; 398 mLastY = y; 399 400 401 break; 402 case MotionEvent.ACTION_UP: 403 case MotionEvent.ACTION_CANCEL: 404 mLastPoint = 0; 405 break; 406 407 } 408 409 return true; 410 } 411 /** 412 * 处理移动后图片边界与屏幕有间隙或者不居中的问题 413 * 这跟我们前面写的代码很像 414 */ 415 private void checkBoderAndCenterWhenMove() { 416 417 RectF rectf = getDrawableRectF(); 418 419 float delaX = 0; 420 float delaY = 0; 421 int width = getWidth(); 422 int height = getHeight(); 423 424 if(rectf.width()>width&&rectf.left>0) 425 { 426 delaX = - rectf.left; 427 } 428 if(rectf.width()>width&&rectf.right<width) 429 { 430 delaX = width - rectf.right; 431 } 432 if(rectf.height()>height&&rectf.top>0) 433 { 434 delaY = - rectf.top; 435 } 436 if(rectf.height()>height&&rectf.bottom<height) 437 { 438 delaY = height - rectf.bottom; 439 } 440 441 scaleMatrix.postTranslate(delaX, delaY); 442 } 443 /** 444 * 判断是否触发移动效果 445 * @param dx 446 * @param dy 447 * @return 448 */ 449 private boolean isCanDrag(float dx, float dy) { 450 451 return Math.sqrt(dx*dx+dy*dy)>mScaleSlop; 452 } 453 454 455 456 457 }
红色部分就是增加的代码了。在这里简单解释一下。首先拿到系统给定的判定是否触发移动效果的临界值,即91行获得mScaleSlop,然后在isCanDrag方法中根据移动的距离,判定是否足够触发移动效果。在onTouch方法中,编写具体的移动逻辑。由于是多点触控,即屏幕上可能不止一个手指。因此通过计算得到中心触控的位置x和y,即第349行和350行所做的事情。另外通过mLastX和mLastY保存之前移动的中心位置。通过计算当前中心位置与之前保存的中心位置的差值,就可以得到需要图片移动的距离了,即第368和第369行代码所做的事情。之后就可以判断是否触发移动了,如果触发了那就移动呗。移动完成后,再做些善后处理。大体逻辑就是这样子。很简单,代码也很详实。不再解释了。哦,对了,因为移动过程中,也可能会出现与屏幕间有空隙,因此需要checkBoderAndCenterWhenMove一下。这里面的代码跟之前写的的差不多了,就不多余解释了,原理一样。
好了,运行程序吧,效果如下:
效果达到了。是不是发现代码越写越少,越简单呢?因为这个项目快要完成了,基本没有什么大的逻辑了。下面就 快马加鞭,实现双击放大与缩小图片的功能吧:《(四)双击放大与缩小图片》