├── ScaleView.gif ├── README.md └── ScaleView.java /ScaleView.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ccapton/Android-ScaleView/HEAD/ScaleView.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScaleView 2 | 3 | Android 可自由移动放大缩小的图片控件ScaleView 4 | 5 | ![](https://raw.githubusercontent.com/Ccapton/Android-ScaleView/master/ScaleView.gif) 6 | 7 | 直接复制ScaleView.java代码即可使用 8 | -------------------------------------------------------------------------------- /ScaleView.java: -------------------------------------------------------------------------------- 1 | package com.capton.sl; 2 | /** 3 | * 可以自由移动缩放的图片控件 4 | * Created by capton on 2017/4/18. 5 | */ 6 | 7 | import android.content.Context; 8 | import android.graphics.Matrix; 9 | import android.graphics.RectF; 10 | import android.graphics.drawable.Drawable; 11 | import android.util.AttributeSet; 12 | import android.view.GestureDetector; 13 | import android.view.MotionEvent; 14 | import android.view.ScaleGestureDetector; 15 | import android.view.ScaleGestureDetector.OnScaleGestureListener; 16 | import android.view.View; 17 | import android.view.View.OnTouchListener; 18 | import android.view.ViewConfiguration; 19 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 20 | import android.widget.ImageView; 21 | 22 | 23 | public class ScaleView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener, OnTouchListener { 24 | 25 | /** 表示是否只有一次加载 */ 26 | private boolean isOnce = false; 27 | /** 初始时的缩放值 */ 28 | private float mInitScale; 29 | /** 双击时 的缩放值 */ 30 | private float mClickScale; 31 | /** 最大的缩放值 */ 32 | private float mMaxScale; 33 | /** 图片缩放矩阵 */ 34 | private Matrix mMatrix; 35 | /** 图片缩放手势 */ 36 | private ScaleGestureDetector mScaleGesture; 37 | 38 | // ----------------------------自由移动-------------------------------- 39 | /** 可移动最短距离限制,大于这个值时就可移动 */ 40 | private int mTouchSlop; 41 | /** 是否可以拖动 */ 42 | private boolean isCanDrag; 43 | 44 | // ----------------------------双击放大-------------------------------- 45 | private GestureDetector mGesture; 46 | // 是否自动缩放 47 | private boolean isAutoScale; 48 | 49 | public ScaleView(Context context, AttributeSet attrs) { 50 | this(context, attrs, 0); 51 | } 52 | 53 | public ScaleView(Context context) { 54 | this(context, null); 55 | } 56 | 57 | public ScaleView(Context context, AttributeSet attrs, int defStyle) { 58 | super(context, attrs, defStyle); 59 | // 必须设置才能触发 60 | this.setOnTouchListener(this); 61 | 62 | mMatrix = new Matrix(); 63 | // 设置缩放模式 64 | super.setScaleType(ScaleType.MATRIX); 65 | 66 | mScaleGesture = new ScaleGestureDetector(context, this); 67 | mGesture = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { 68 | @Override 69 | public boolean onDoubleTap(MotionEvent e) { 70 | 71 | // 如果正在缩放时,不能放大 72 | if (isAutoScale) { 73 | return true; 74 | } 75 | 76 | float px = e.getX(); 77 | float py = e.getY(); 78 | // 只有小于最大缩放比例才能放大 79 | float scale = getScale(); 80 | if (scale < mClickScale) { 81 | // mMatrix.postScale(mClickScale/scale, mClickScale/scale, 82 | // px, py); 83 | postDelayed(new ScaleRunnale(px, py, mClickScale), 16); 84 | isAutoScale = true; 85 | } else { 86 | // mMatrix.postScale(mInitScale/scale, mInitScale/scale, px, 87 | // py); 88 | postDelayed(new ScaleRunnale(px, py, mInitScale), 16); 89 | isAutoScale = true; 90 | } 91 | // setImageMatrix(mMatrix); 92 | return true; 93 | } 94 | }); 95 | 96 | /** 97 | * 是一个距离,表示滑动的时候,手的移动要大于这个距离才开始移动控件。如果小于这个距离就不触发移动控件,如viewpager 98 | * 就是用这个距离来判断用户是否翻页。 99 | */ 100 | mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 101 | } 102 | 103 | private class ScaleRunnale implements Runnable { 104 | // 放大值 105 | private static final float BIGGER = 1.08f; 106 | // 缩小值 107 | private static final float SMALLER = 0.96f; 108 | private float x; 109 | private float y; 110 | private float mTargetScale; 111 | private float mTempScale; 112 | 113 | public ScaleRunnale(float x, float y, float mTargetScale) { 114 | super(); 115 | this.x = x; 116 | this.y = y; 117 | this.mTargetScale = mTargetScale; 118 | 119 | if (getScale() < mTargetScale) { 120 | mTempScale = BIGGER; 121 | } else if (getScale() > mTargetScale) { 122 | mTempScale = SMALLER; 123 | } 124 | } 125 | 126 | @Override 127 | public void run() { 128 | // 先进行缩放 129 | mMatrix.postScale(mTempScale, mTempScale, x, y); 130 | checkSideAndCenterWhenScale(); 131 | setImageMatrix(mMatrix); 132 | 133 | float currentScale = getScale(); 134 | 135 | // 如果想放大,并且当前的缩放值小于目标值 136 | if ((mTempScale > 1.0f && currentScale < mTargetScale) 137 | || (mTempScale < 1.0f && currentScale > mTargetScale)) { 138 | // 递归执行run方法 139 | postDelayed(this, 16); 140 | } else { 141 | float scale = mTargetScale / currentScale; 142 | mMatrix.postScale(scale, scale, x, y); 143 | checkSideAndCenterWhenScale(); 144 | setImageMatrix(mMatrix); 145 | 146 | isAutoScale = false; 147 | } 148 | } 149 | 150 | } 151 | 152 | @Override 153 | public void onGlobalLayout() { 154 | // 如果还没有加载图片 155 | if (!isOnce) { 156 | 157 | // 获得控件的宽高 158 | int width = getWidth(); 159 | int height = getHeight(); 160 | 161 | Drawable drawable = getDrawable(); 162 | if (drawable == null) { 163 | return; 164 | } 165 | // 获得图片的宽高 166 | int bitmapWidth = drawable.getIntrinsicWidth(); 167 | int bitmapHeight = drawable.getIntrinsicHeight(); 168 | 169 | // 设定比例值 170 | float scale = 0.0f; 171 | 172 | // 如果图片的宽度>控件的宽度,缩小 173 | if (bitmapWidth > width && bitmapHeight < height) { 174 | scale = width * 1.0f / bitmapWidth; 175 | } 176 | // 如果图片的高度>控件的高度,缩小 177 | if (bitmapHeight > height && bitmapWidth < width) { 178 | scale = height * 1.0f / bitmapHeight; 179 | } 180 | // 如果图片的宽高度>控件的宽高度,缩小 或者 如果图片的宽高度<控件的宽高度,放大 181 | if ((bitmapWidth > width && bitmapHeight > height) || (bitmapWidth < width && bitmapHeight < height)) { 182 | float f1 = width * 1.0f / bitmapWidth; 183 | float f2 = height * 1.0f / bitmapHeight; 184 | scale = Math.min(f1, f2); 185 | } 186 | 187 | // 初始化缩放值 188 | mInitScale = scale; 189 | mClickScale = mInitScale * 2; 190 | mMaxScale = mInitScale * 4; 191 | 192 | // 得到移动的距离 193 | int dx = width / 2 - bitmapWidth / 2; 194 | int dy = height / 2 - bitmapHeight / 2; 195 | 196 | // 平移 197 | mMatrix.postTranslate(dx, dy); 198 | 199 | // 在控件的中心缩放 200 | mMatrix.postScale(scale, scale, width / 2, height / 2); 201 | 202 | // 设置矩阵 203 | setImageMatrix(mMatrix); 204 | 205 | // 关于matrix,就是个3*3的矩阵 206 | /** 207 | * xscale xskew xtrans yskew yscale ytrans 0 0 0 208 | */ 209 | 210 | isOnce = true; 211 | } 212 | } 213 | 214 | /** 215 | * 注册全局事件 216 | */ 217 | @Override 218 | protected void onAttachedToWindow() { 219 | super.onAttachedToWindow(); 220 | getViewTreeObserver().addOnGlobalLayoutListener(this); 221 | } 222 | 223 | /** 224 | * 移除全局事件 225 | */ 226 | @Override 227 | protected void onDetachedFromWindow() { 228 | super.onDetachedFromWindow(); 229 | getViewTreeObserver().removeGlobalOnLayoutListener(this); 230 | } 231 | 232 | /** 233 | * 获得缩放值 234 | * 235 | * @return 236 | */ 237 | public float getScale() { 238 | /** 239 | * xscale xskew xtrans yskew yscale ytrans 0 0 0 240 | */ 241 | float[] values = new float[9]; 242 | mMatrix.getValues(values); 243 | return values[Matrix.MSCALE_X]; 244 | } 245 | 246 | @Override 247 | public boolean onScale(ScaleGestureDetector detector) { 248 | // 如果没有图片,返回 249 | if (getDrawable() == null) { 250 | return true; 251 | } 252 | // 缩放因子,>0表示正在放大,<0表示正在缩小 253 | float intentScale = detector.getScaleFactor(); 254 | float scale = getScale(); 255 | 256 | // 进行缩放范围的控制 257 | // 判断,如果<最大缩放值,表示可以放大,如果》最小缩放,说明可以缩小 258 | if ((scale < mMaxScale && intentScale > 1.0f) || (scale > mInitScale && intentScale < 1.0f)) { 259 | 260 | // scale 变小时, intentScale变小 261 | if (scale * intentScale < mInitScale) { 262 | // intentScale * scale = mInitScale ; 263 | intentScale = mInitScale / scale; 264 | } 265 | 266 | // scale 变大时, intentScale变大 267 | if (scale * intentScale > mMaxScale) { 268 | // intentScale * scale = mMaxScale ; 269 | intentScale = mMaxScale / scale; 270 | } 271 | 272 | // 以控件为中心缩放 273 | // mMatrix.postScale(intentScale, intentScale, getWidth()/2, 274 | // getHeight()/2); 275 | // 以手势为中心缩放 276 | mMatrix.postScale(intentScale, intentScale, detector.getFocusX(), detector.getFocusY()); 277 | 278 | // 检测边界与中心点 279 | checkSideAndCenterWhenScale(); 280 | 281 | setImageMatrix(mMatrix); 282 | } 283 | 284 | return true; 285 | } 286 | 287 | /** 288 | * 获得图片缩放后的矩阵 289 | * 290 | * @return 291 | */ 292 | public RectF getMatrixRectF() { 293 | Matrix matrix = mMatrix; 294 | RectF rectF = new RectF(); 295 | Drawable drawable = getDrawable(); 296 | if (drawable != null) { 297 | // 初始化矩阵 298 | rectF.set(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight()); 299 | // 移动s 300 | matrix.mapRect(rectF); 301 | } 302 | return rectF; 303 | } 304 | 305 | private void checkSideAndCenterWhenScale() { 306 | RectF rectF = getMatrixRectF(); 307 | float deltaX = 0f; 308 | float deltaY = 0f; 309 | int width = getWidth(); 310 | int height = getHeight(); 311 | 312 | // 情况1, 如果图片的宽度大于控件的宽度 313 | if (rectF.width() >= width) { 314 | if (rectF.left > 0) { 315 | deltaX = -rectF.left;// 如果图片没有左边对齐,就往左边移动 316 | } 317 | if (rectF.right < width) { 318 | deltaX = width - rectF.right;// 如果图片没有右边对齐,就往右边移动 319 | } 320 | } 321 | // 情况2, 如果图片的宽度大于控件的宽度 322 | if (rectF.height() >= height) { 323 | if (rectF.top > 0) { 324 | deltaY = -rectF.top;// 325 | } 326 | if (rectF.bottom < height) { 327 | deltaY = height - rectF.bottom;// 往底部移动 328 | } 329 | } 330 | 331 | // 情况3,如图图片在控件内,则让其居中 332 | if (rectF.width() < width) { 333 | // deltaX = width/2-rectF.left - rectF.width()/2; 334 | // 或 335 | deltaX = width / 2f - rectF.right + rectF.width() / 2f; 336 | } 337 | 338 | if (rectF.height() < height) { 339 | deltaY = height / 2f - rectF.bottom + rectF.height() / 2f; 340 | } 341 | 342 | mMatrix.postTranslate(deltaX, deltaY); 343 | } 344 | 345 | @Override 346 | public boolean onScaleBegin(ScaleGestureDetector detector) { 347 | // TODO Auto-generated method stub 348 | return true; 349 | } 350 | 351 | @Override 352 | public void onScaleEnd(ScaleGestureDetector detector) { 353 | // TODO Auto-generated method stub 354 | 355 | } 356 | 357 | private float mLastX; 358 | private float mLastY; 359 | /** 上次手指的数量 */ 360 | private int mLastPointerCount; 361 | 362 | /** 判断是否检测了x,y轴 */ 363 | private boolean isCheckX; 364 | private boolean isCheckY; 365 | 366 | @Override 367 | public boolean onTouch(View v, MotionEvent event) { 368 | 369 | // 把事件传递给双击手势 370 | if (mGesture.onTouchEvent(event)) { 371 | return true; 372 | } 373 | // 把事件传递给缩放手势 374 | mScaleGesture.onTouchEvent(event); 375 | 376 | float x = event.getX(); 377 | float y = event.getY(); 378 | 379 | int pointerCount = event.getPointerCount(); 380 | for (int i = 0; i < pointerCount; i++) { 381 | x += event.getX(i); 382 | y += event.getY(i); 383 | } 384 | x /= pointerCount; 385 | y /= pointerCount; 386 | 387 | // 说明手指改变 388 | if (mLastPointerCount != pointerCount) { 389 | isCanDrag = false; 390 | mLastX = x; 391 | mLastY = y; 392 | } 393 | mLastPointerCount = pointerCount; 394 | 395 | RectF rectF = getMatrixRectF(); 396 | 397 | switch (event.getAction()) { 398 | case MotionEvent.ACTION_DOWN: 399 | if (rectF.width() > getWidth()) { 400 | getParent().requestDisallowInterceptTouchEvent(true); 401 | } 402 | break; 403 | 404 | case MotionEvent.ACTION_MOVE: 405 | if (rectF.width() > getWidth()) { 406 | getParent().requestDisallowInterceptTouchEvent(true); 407 | } 408 | 409 | float dx = x - mLastX; 410 | float dy = y - mLastY; 411 | 412 | if (!isCanDrag) { 413 | isCanDrag = isMoveAction(dx, dy); 414 | } 415 | /** 416 | * 如果能移动 417 | */ 418 | if (isCanDrag) { 419 | //RectF rectF = getMatrixRectF(); 420 | if (getDrawable() == null) { 421 | return true; 422 | } 423 | 424 | isCheckX = isCheckY = true; 425 | 426 | // 如果图片在控件内,不允许移动 427 | if (rectF.width() < getWidth()) { 428 | isCheckX = false; 429 | dx = 0f; 430 | } 431 | if (rectF.height() < getHeight()) { 432 | isCheckY = false; 433 | dy = 0f; 434 | } 435 | 436 | mMatrix.postTranslate(dx, dy); 437 | 438 | // 移动事检测边界 439 | checkSideAndCenterWhenTransate(); 440 | 441 | setImageMatrix(mMatrix); 442 | } 443 | 444 | mLastX = x; 445 | mLastY = y; 446 | 447 | break; 448 | 449 | case MotionEvent.ACTION_UP: 450 | case MotionEvent.ACTION_CANCEL: 451 | // 清楚手指 452 | mLastPointerCount = 0; 453 | 454 | break; 455 | } 456 | 457 | return true; 458 | } 459 | 460 | private void checkSideAndCenterWhenTransate() { 461 | RectF rectF = getMatrixRectF(); 462 | float deltaX = 0f; 463 | float deltaY = 0f; 464 | int width = getWidth(); 465 | int height = getHeight(); 466 | 467 | if (rectF.top > 0 && isCheckY) { 468 | deltaY = -rectF.top;// 往上边移动 469 | } 470 | if (rectF.bottom < height && isCheckY) { 471 | deltaY = height - rectF.bottom;// 往底部移动 472 | } 473 | 474 | if (rectF.left > 0 && isCheckX) { 475 | deltaX = -rectF.left;// 往左边移动 476 | } 477 | if (rectF.right < width && isCheckX) { 478 | deltaX = width - rectF.right;// 往右边移动 479 | } 480 | // 移动 481 | mMatrix.postTranslate(deltaX, deltaY); 482 | } 483 | 484 | private boolean isMoveAction(float dx, float dy) { 485 | // 求得两点的距离 486 | return Math.sqrt(dx * dx + dy * dy) > mTouchSlop; 487 | } 488 | 489 | } 490 | --------------------------------------------------------------------------------