生活中经常会用到图片放大和缩小,今天简单学习下.
思路:1.添加一个操作图片放大和缩小类; 2. 布局文件中引用这个自定义控件; 3. 主Activity一些修改. 代码如下:
增加图片操作类:
1 package com.example.imagezoomdemo; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 import android.content.Context; 7 import android.graphics.Bitmap; 8 import android.graphics.Canvas; 9 import android.graphics.Paint; 10 import android.graphics.Rect; 11 import android.util.AttributeSet; 12 import android.util.Log; 13 import android.view.MotionEvent; 14 import android.view.View; 15 16 public class zoomimage extends View implements Observer { 17 18 /** Paint object used when drawing bitmap. */ 19 private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG); 20 21 /** Rectangle used (and re-used) for cropping source image. */ 22 private final Rect mRectSrc = new Rect(); 23 24 /** Rectangle used (and re-used) for specifying drawing area on canvas. */ 25 private final Rect mRectDst = new Rect(); 26 27 /** Object holding aspect quotient */ 28 private final AspectQuotient mAspectQuotient = new AspectQuotient(); 29 30 /** The bitmap that we're zooming in, and drawing on the screen. */ 31 private Bitmap mBitmap; 32 33 /** State of the zoom. */ 34 private ZoomState mState; 35 36 private BasicZoomControl mZoomControl; 37 private BasicZoomListener mZoomListener; 38 39 public zoomimage(Context context, AttributeSet attrs) { 40 super(context, attrs); 41 42 mZoomControl = new BasicZoomControl(); 43 44 mZoomListener = new BasicZoomListener(); 45 mZoomListener.setZoomControl(mZoomControl); 46 47 setZoomState(mZoomControl.getZoomState()); 48 49 setOnTouchListener(mZoomListener); 50 51 mZoomControl.setAspectQuotient(getAspectQuotient()); 52 } 53 54 public void zoomImage(float f, float x, float y) { 55 mZoomControl.zoom(f, x, y); 56 } 57 58 public void setImage(Bitmap bitmap) { 59 mBitmap = bitmap; 60 61 mAspectQuotient.updateAspectQuotient(getWidth(), getHeight(), 62 mBitmap.getWidth(), mBitmap.getHeight()); 63 mAspectQuotient.notifyObservers(); 64 65 invalidate(); 66 } 67 68 private void setZoomState(ZoomState state) { 69 if (mState != null) { 70 mState.deleteObserver(this); 71 } 72 73 mState = state; 74 mState.addObserver(this); 75 76 invalidate(); 77 } 78 79 private AspectQuotient getAspectQuotient() { 80 return mAspectQuotient; 81 } 82 83 @Override 84 protected void onDraw(Canvas canvas) { 85 if (mBitmap != null && mState != null) { 86 87 Log.d("ZoomImageView", "OnDraw"); 88 89 final float aspectQuotient = mAspectQuotient.get(); 90 91 final int viewWidth = getWidth(); 92 final int viewHeight = getHeight(); 93 final int bitmapWidth = mBitmap.getWidth(); 94 final int bitmapHeight = mBitmap.getHeight(); 95 96 Log.d("ZoomImageView", "viewWidth = " + viewWidth); 97 Log.d("ZoomImageView", "viewHeight = " + viewHeight); 98 Log.d("ZoomImageView", "bitmapWidth = " + bitmapWidth); 99 Log.d("ZoomImageView", "bitmapHeight = " + bitmapHeight); 100 101 final float panX = mState.getPanX(); 102 final float panY = mState.getPanY(); 103 final float zoomX = mState.getZoomX(aspectQuotient) * viewWidth 104 / bitmapWidth; 105 final float zoomY = mState.getZoomY(aspectQuotient) * viewHeight 106 / bitmapHeight; 107 108 // Setup source and destination rectangles 109 mRectSrc.left = (int) (panX * bitmapWidth - viewWidth / (zoomX * 2)); 110 mRectSrc.top = (int) (panY * bitmapHeight - viewHeight 111 / (zoomY * 2)); 112 mRectSrc.right = (int) (mRectSrc.left + viewWidth / zoomX); 113 mRectSrc.bottom = (int) (mRectSrc.top + viewHeight / zoomY); 114 // mRectDst.left = getLeft(); 115 mRectDst.left = 0; 116 mRectDst.top = 0; 117 // mRectDst.right = getRight(); 118 mRectDst.right = getWidth(); 119 mRectDst.bottom = getHeight(); 120 121 // Adjust source rectangle so that it fits within the source image. 122 if (mRectSrc.left < 0) { 123 mRectDst.left += -mRectSrc.left * zoomX; 124 mRectSrc.left = 0; 125 } 126 if (mRectSrc.right > bitmapWidth) { 127 mRectDst.right -= (mRectSrc.right - bitmapWidth) * zoomX; 128 mRectSrc.right = bitmapWidth; 129 } 130 if (mRectSrc.top < 0) { 131 mRectDst.top += -mRectSrc.top * zoomY; 132 mRectSrc.top = 0; 133 } 134 if (mRectSrc.bottom > bitmapHeight) { 135 mRectDst.bottom -= (mRectSrc.bottom - bitmapHeight) * zoomY; 136 mRectSrc.bottom = bitmapHeight; 137 } 138 139 mRectDst.left = 0; 140 mRectDst.top = 0; 141 mRectDst.right = viewWidth; 142 mRectDst.bottom = viewHeight; 143 144 Log.d("ZoomImageView", "mRectSrc.top" + mRectSrc.top); 145 Log.d("ZoomImageView", "mRectSrc.bottom" + mRectSrc.bottom); 146 Log.d("ZoomImageView", "mRectSrc.left" + mRectSrc.left); 147 Log.d("ZoomImageView", "mRectSrc.right" + mRectSrc.right); 148 149 Log.d("ZoomImageView", "mRectDst.top" + mRectDst.top); 150 Log.d("ZoomImageView", "mRectDst.bottom" + mRectDst.bottom); 151 Log.d("ZoomImageView", "mRectDst.left" + mRectDst.left); 152 Log.d("ZoomImageView", "mRectDst.right" + mRectDst.right); 153 154 canvas.drawBitmap(mBitmap, mRectSrc, mRectDst, mPaint); 155 } 156 } 157 158 @Override 159 protected void onLayout(boolean changed, int left, int top, int right, 160 int bottom) { 161 super.onLayout(changed, left, top, right, bottom); 162 163 mAspectQuotient.updateAspectQuotient(right - left, bottom - top, 164 mBitmap.getWidth(), mBitmap.getHeight()); 165 mAspectQuotient.notifyObservers(); 166 } 167 168 @Override 169 public void update(Observable observable, Object data) { 170 invalidate(); 171 } 172 173 private class BasicZoomListener implements View.OnTouchListener { 174 175 /** Zoom control to manipulate */ 176 private BasicZoomControl mZoomControl; 177 178 private float mFirstX = -1; 179 private float mFirstY = -1; 180 private float mSecondX = -1; 181 private float mSecondY = -1; 182 183 private int mOldCounts = 0; 184 185 /** 186 * Sets the zoom control to manipulate 187 * 188 * @param control 189 * Zoom control 190 */ 191 public void setZoomControl(BasicZoomControl control) { 192 mZoomControl = control; 193 } 194 195 public boolean onTouch(View v, MotionEvent event) { 196 197 switch (event.getAction()) { 198 case MotionEvent.ACTION_DOWN: 199 mOldCounts = 1; 200 mFirstX = event.getX(); 201 mFirstY = event.getY(); 202 break; 203 case MotionEvent.ACTION_MOVE: { 204 float fFirstX = event.getX(); 205 float fFirstY = event.getY(); 206 207 int nCounts = event.getPointerCount(); 208 209 if (1 == nCounts) { 210 mOldCounts = 1; 211 float dx = (fFirstX - mFirstX) / v.getWidth(); 212 float dy = (fFirstY - mFirstY) / v.getHeight(); 213 mZoomControl.pan(-dx, -dy); 214 } else if (1 == mOldCounts) { 215 mSecondX = event.getX(event.getPointerId(nCounts - 1)); 216 mSecondY = event.getY(event.getPointerId(nCounts - 1)); 217 mOldCounts = nCounts; 218 } else { 219 float fSecondX = event 220 .getX(event.getPointerId(nCounts - 1)); 221 float fSecondY = event 222 .getY(event.getPointerId(nCounts - 1)); 223 224 double nLengthOld = getLength(mFirstX, mFirstY, mSecondX, 225 mSecondY); 226 double nLengthNow = getLength(fFirstX, fFirstY, fSecondX, 227 fSecondY); 228 229 float d = (float) ((nLengthNow - nLengthOld) / v.getWidth()); 230 231 mZoomControl.zoom((float) Math.pow(20, d), 232 ((fFirstX + fSecondX) / 2 / v.getWidth()), 233 ((fFirstY + fSecondY) / 2 / v.getHeight())); 234 235 mSecondX = fSecondX; 236 mSecondY = fSecondY; 237 } 238 mFirstX = fFirstX; 239 mFirstY = fFirstY; 240 241 break; 242 } 243 244 } 245 246 return true; 247 } 248 249 private double getLength(float x1, float y1, float x2, float y2) { 250 return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)); 251 } 252 } 253 254 private class BasicZoomControl implements Observer { 255 256 /** Minimum zoom level limit */ 257 private static final float MIN_ZOOM = 1; 258 259 /** Maximum zoom level limit */ 260 private static final float MAX_ZOOM = 16; 261 262 /** Zoom state under control */ 263 private final ZoomState mState = new ZoomState(); 264 265 /** Object holding aspect quotient of view and content */ 266 private AspectQuotient mAspectQuotient; 267 268 /** 269 * Set reference object holding aspect quotient 270 * 271 * @param aspectQuotient 272 * Object holding aspect quotient 273 */ 274 public void setAspectQuotient(AspectQuotient aspectQuotient) { 275 if (mAspectQuotient != null) { 276 mAspectQuotient.deleteObserver(this); 277 } 278 279 mAspectQuotient = aspectQuotient; 280 mAspectQuotient.addObserver(this); 281 } 282 283 /** 284 * Get zoom state being controlled 285 * 286 * @return The zoom state 287 */ 288 public ZoomState getZoomState() { 289 return mState; 290 } 291 292 /** 293 * Zoom 294 * 295 * @param f 296 * Factor of zoom to apply 297 * @param x 298 * X-coordinate of invariant position 299 * @param y 300 * Y-coordinate of invariant position 301 */ 302 public void zoom(float f, float x, float y) { 303 304 // Log.d("Zoom", "zoom f = " + f); 305 306 final float aspectQuotient = mAspectQuotient.get(); 307 308 final float prevZoomX = mState.getZoomX(aspectQuotient); 309 final float prevZoomY = mState.getZoomY(aspectQuotient); 310 311 mState.setZoom(mState.getZoom() * f); 312 limitZoom(); 313 314 final float newZoomX = mState.getZoomX(aspectQuotient); 315 final float newZoomY = mState.getZoomY(aspectQuotient); 316 317 // Pan to keep x and y coordinate invariant 318 mState.setPanX(mState.getPanX() + (x - .5f) 319 * (1f / prevZoomX - 1f / newZoomX)); 320 mState.setPanY(mState.getPanY() + (y - .5f) 321 * (1f / prevZoomY - 1f / newZoomY)); 322 323 limitPan(); 324 325 mState.notifyObservers(); 326 } 327 328 /** 329 * Pan 330 * 331 * @param dx 332 * Amount to pan in x-dimension 333 * @param dy 334 * Amount to pan in y-dimension 335 */ 336 public void pan(float dx, float dy) { 337 final float aspectQuotient = mAspectQuotient.get(); 338 339 mState.setPanX(mState.getPanX() + dx 340 / mState.getZoomX(aspectQuotient)); 341 mState.setPanY(mState.getPanY() + dy 342 / mState.getZoomY(aspectQuotient)); 343 344 limitPan(); 345 346 mState.notifyObservers(); 347 } 348 349 /** 350 * Help function to figure out max delta of pan from center position. 351 * 352 * @param zoom 353 * Zoom value 354 * @return Max delta of pan 355 */ 356 private float getMaxPanDelta(float zoom) { 357 return Math.max(0f, .5f * ((zoom - 1) / zoom)); 358 } 359 360 /** 361 * Force zoom to stay within limits 362 */ 363 private void limitZoom() { 364 if (mState.getZoom() < MIN_ZOOM) { 365 mState.setZoom(MIN_ZOOM); 366 } else if (mState.getZoom() > MAX_ZOOM) { 367 mState.setZoom(MAX_ZOOM); 368 } 369 } 370 371 /** 372 * Force pan to stay within limits 373 */ 374 private void limitPan() { 375 final float aspectQuotient = mAspectQuotient.get(); 376 377 final float zoomX = mState.getZoomX(aspectQuotient); 378 final float zoomY = mState.getZoomY(aspectQuotient); 379 380 final float panMinX = .5f - getMaxPanDelta(zoomX); 381 final float panMaxX = .5f + getMaxPanDelta(zoomX); 382 final float panMinY = .5f - getMaxPanDelta(zoomY); 383 final float panMaxY = .5f + getMaxPanDelta(zoomY); 384 385 if (mState.getPanX() < panMinX) { 386 mState.setPanX(panMinX); 387 } 388 if (mState.getPanX() > panMaxX) { 389 mState.setPanX(panMaxX); 390 } 391 if (mState.getPanY() < panMinY) { 392 mState.setPanY(panMinY); 393 } 394 if (mState.getPanY() > panMaxY) { 395 mState.setPanY(panMaxY); 396 } 397 } 398 399 // Observable interface implementation 400 401 public void update(Observable observable, Object data) { 402 limitZoom(); 403 limitPan(); 404 } 405 } 406 407 private class AspectQuotient extends Observable { 408 409 /** 410 * Aspect quotient 411 */ 412 private float mAspectQuotient; 413 414 // Public methods 415 416 /** 417 * Gets aspect quotient 418 * 419 * @return The aspect quotient 420 */ 421 public float get() { 422 return mAspectQuotient; 423 } 424 425 /** 426 * Updates and recalculates aspect quotient based on supplied view and 427 * content dimensions. 428 * 429 * @param viewWidth 430 * Width of view 431 * @param viewHeight 432 * Height of view 433 * @param contentWidth 434 * Width of content 435 * @param contentHeight 436 * Height of content 437 */ 438 public void updateAspectQuotient(float viewWidth, float viewHeight, 439 float contentWidth, float contentHeight) { 440 final float aspectQuotient = (contentWidth / contentHeight) 441 / (viewWidth / viewHeight); 442 443 if (aspectQuotient != mAspectQuotient) { 444 mAspectQuotient = aspectQuotient; 445 setChanged(); 446 } 447 } 448 } 449 450 private class ZoomState extends Observable { 451 /** 452 * Zoom level A value of 1.0 means the content fits the view. 453 */ 454 private float mZoom; 455 456 /** 457 * Pan position x-coordinate X-coordinate of zoom window center 458 * position, relative to the width of the content. 459 */ 460 private float mPanX; 461 462 /** 463 * Pan position y-coordinate Y-coordinate of zoom window center 464 * position, relative to the height of the content. 465 */ 466 private float mPanY; 467 468 // Public methods 469 470 /** 471 * Get current x-pan 472 * 473 * @return current x-pan 474 */ 475 public float getPanX() { 476 return mPanX; 477 } 478 479 /** 480 * Get current y-pan 481 * 482 * @return Current y-pan 483 */ 484 public float getPanY() { 485 return mPanY; 486 } 487 488 /** 489 * Get current zoom value 490 * 491 * @return Current zoom value 492 */ 493 public float getZoom() { 494 return mZoom; 495 } 496 497 /** 498 * Help function for calculating current zoom value in x-dimension 499 * 500 * @param aspectQuotient 501 * (Aspect ratio content) / (Aspect ratio view) 502 * @return Current zoom value in x-dimension 503 */ 504 public float getZoomX(float aspectQuotient) { 505 return Math.min(mZoom, mZoom * aspectQuotient); 506 } 507 508 /** 509 * Help function for calculating current zoom value in y-dimension 510 * 511 * @param aspectQuotient 512 * (Aspect ratio content) / (Aspect ratio view) 513 * @return Current zoom value in y-dimension 514 */ 515 public float getZoomY(float aspectQuotient) { 516 return Math.min(mZoom, mZoom / aspectQuotient); 517 } 518 519 /** 520 * Set pan-x 521 * 522 * @param panX 523 * Pan-x value to set 524 */ 525 public void setPanX(float panX) { 526 if (panX != mPanX) { 527 mPanX = panX; 528 setChanged(); 529 } 530 } 531 532 /** 533 * Set pan-y 534 * 535 * @param panY 536 * Pan-y value to set 537 */ 538 public void setPanY(float panY) { 539 if (panY != mPanY) { 540 mPanY = panY; 541 setChanged(); 542 } 543 } 544 545 /** 546 * Set zoom 547 * 548 * @param zoom 549 * Zoom value to set 550 */ 551 public void setZoom(float zoom) { 552 if (zoom != mZoom) { 553 mZoom = zoom; 554 setChanged(); 555 } 556 } 557 } 558 }
布局修改:
1 <?xml version="1.0" encoding="utf-8"?> 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 3 android:layout_width="fill_parent" 4 android:layout_height="fill_parent" 5 android:orientation="vertical" > 6 7 <!-- 引用自定义控件 --> 8 <com.example.imagezoomdemo.zoomimage 9 android:id="@+id/image" 10 android:layout_width="wrap_content" 11 android:layout_height="wrap_content" > 12 </com.example.imagezoomdemo.zoomimage> 13 14 </LinearLayout>
最后修改下Activity:
1 package com.example.imagezoomdemo; 2 3 import android.app.Activity; 4 import android.os.Bundle; 5 import android.graphics.Bitmap; 6 import android.graphics.BitmapFactory; 7 8 public class MainActivity extends Activity { 9 10 private zoomimage zoomImg; 11 12 @Override 13 protected void onCreate(Bundle savedInstanceState) { 14 super.onCreate(savedInstanceState); 15 setContentView(R.layout.activity_main); 16 17 zoomImg = (zoomimage) findViewById(R.id.image); 18 Bitmap bitmap = BitmapFactory.decodeResource(this.getResources(), 19 R.drawable.aa); 20 zoomImg.setImage(bitmap); 21 } 22 23 }
运行效果:
(1)原图:
(2) 放大:
(3) 缩小