自定義ImageView: 實現自由縮放 ,自由移動縮放後的圖片 .雙擊放大與縮小圖片 相容ViewPager
阿新 • • 發佈:2018-12-14
直接擼程式碼, 複製就能用
package com.zhf.baselibrary.view; import android.annotation.SuppressLint; import android.content.Context; import android.graphics.Matrix; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.ScaleGestureDetector.OnScaleGestureListener; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewConfiguration; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.ImageView; /** * 自定義ImageView: * 1. 初步實現多點觸控、自由縮放 ,處理圖片自由縮放出現的間隙 預設 * 2. 多點觸控之自由移動縮放後的圖片 * 3. 雙擊放大與縮小圖片 * 4. 相容ViewPager */ public class ZoomImageView extends ImageView implements OnGlobalLayoutListener, OnScaleGestureListener, OnTouchListener { private Context context; //上下文 private boolean mOnce = false;//是否執行了一次 /** * 初始縮放的比例 */ private float initScale; /** * 縮放比例 */ private float midScale; /** * 可放大的最大比例 */ private float maxScale; /** * 可放大的最大比例 */ private float minScale; /** * 縮放矩陣 */ private Matrix scaleMatrix; /** * 縮放的手勢監控類 */ private ScaleGestureDetector mScaleGestureDetector; /** * 上一次移動的手指個數,也可以說是多點個數 */ private int mLastPoint; /** * 上次的中心點的x位置 */ private float mLastX; /** * 上一次中心點的y位置 */ private float mLastY; /** * 一個臨界值,即是否觸發移動的臨界值 */ private float mScaleSlop; /** * 是否可移動 */ private boolean isCanDrag = false; /** * 監測各種手勢事件,例如雙擊 */ private GestureDetector mGestureDetector; /** * 是否正在執行雙擊縮放 */ private boolean isAutoScale; /////////////////////////////////////////////////////////////////////////////// /** * 自定義是否 自由縮放 */ private boolean autoZoom = false; /** * 最大放大倍數 */ private int maxRatio = 4; /** * 最小縮小倍數 */ private int minRatio = 2; public ZoomImageView(Context context) { this(context, null); } public ZoomImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public ZoomImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); this.context = context; //用來操作 image的 矩陣 scaleMatrix = new Matrix(); setScaleType(ScaleType.MATRIX); //手勢回撥 mScaleGestureDetector = new ScaleGestureDetector(context, this); //觸控回撥 setOnTouchListener(this); //獲得系統給定的觸發移動效果的臨界值 mScaleSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } /** * 設定是否支援雙擊放大 */ public void setDoubleZoom(boolean doubleZoom) { //雙擊放大2倍 縮小 if (doubleZoom) { mGestureDetector = new GestureDetector(context, new MySimpleOnGestureListener()); } else { mGestureDetector = null; } } /** * 設定是否支援手勢縮放 */ public void setAutoZoom(boolean autoZoom) { this.autoZoom = autoZoom; } /** * 該方法在view與window繫結時被呼叫,且只會被呼叫一次,其在view的onDraw方法之前呼叫 */ protected void onAttachedToWindow() { super.onAttachedToWindow(); //註冊監聽器 getViewTreeObserver().addOnGlobalLayoutListener(this); } /** * 該方法在view被銷燬時被呼叫 */ @SuppressLint("NewApi") protected void onDetachedFromWindow() { super.onDetachedFromWindow(); //取消監聽器 getViewTreeObserver().removeOnGlobalLayoutListener(this); } /** * 當一個view的佈局載入完成或者佈局發生改變時,OnGlobalLayoutListener會監聽到,呼叫該方法 * 因此該方法可能會被多次呼叫,需要在合適的地方註冊和取消監聽器 * <p> * 圖片居中全屏View顯示 */ public void onGlobalLayout() { if (!mOnce) { //獲得當前view的Drawable Drawable d = getDrawable(); if (d == null) { return; } //獲得Drawable的寬和高 int dw = d.getIntrinsicWidth(); int dh = d.getIntrinsicHeight(); //獲取當前view的寬和高 int width = getWidth(); int height = getHeight(); //縮放的比例,scale可能是縮小的比例也可能是放大的比例,看它的值是大於1還是小於1 float scale = 1.0f; //如果僅僅是圖片寬度比view寬度大,則應該將圖片按寬度縮小 if (dw > width && dh < height) { scale = width * 1.0f / dw; } //如果圖片和高度都比view的大,則應該按最小的比例縮小圖片 if (dw > width && dh > height) { scale = Math.min(width * 1.0f / dw, height * 1.0f / dh); } //如果圖片寬度和高度都比view的要小,則應該按最小的比例放大圖片 if (dw < width && dh < height) { scale = Math.min(width * 1.0f / dw, height * 1.0f / dh); } //如果僅僅是高度比view的大,則按照高度縮小圖片即可 if (dw < width && dh > height) { scale = height * 1.0f / dh; } //初始化縮放的比例 initScale = scale; midScale = initScale * 2; maxScale = initScale * maxRatio; minScale = initScale / minRatio; //移動圖片到達view的中心 int dx = width / 2 - dw / 2; int dy = height / 2 - dh / 2; scaleMatrix.postTranslate(dx, dy); //縮放圖片 scaleMatrix.postScale(initScale, initScale, width / 2, height / 2); setImageMatrix(scaleMatrix); mOnce = true; } } /** * 獲取當前已經縮放的比例 * * @return 因為x方向和y方向比例相同,所以只返回x方向的縮放比例即可 */ private float getDrawableScale() { float[] values = new float[9]; scaleMatrix.getValues(values); return values[Matrix.MSCALE_X]; } /** * 縮放手勢開始時呼叫該方法 */ public boolean onScaleBegin(ScaleGestureDetector detector) { //返回為true,則縮放手勢事件往下進行,否則到此為止,即不會執行onScale和onScaleEnd方法 return autoZoom; } /** * 縮放手勢完成後呼叫該方法 */ public void onScaleEnd(ScaleGestureDetector detector) { } /** * 縮放手勢進行時呼叫該方法 * <p> * 縮放範圍:initScale~maxScale */ public boolean onScale(ScaleGestureDetector detector) { if (getDrawable() == null) { return true;//如果沒有圖片,下面的程式碼沒有必要執行 } //獲取當前已經縮放的比例 float scale = getDrawableScale(); //獲取當前縮放因子,指的是手指縮放手勢 使用者預縮放的值 float scaleFactor = detector.getScaleFactor(); //如果當前縮放比例比最大比例小,且縮放因子大於1,說明想放大,這是被允許的,因為還還可以再放大。 //如果當前縮放比例比最小比例大,且縮放因子小於1,說明想縮小,這也是被允許的。 if ((scale < maxScale && scaleFactor > 1.0f) || (scale > minScale && scaleFactor < 1.0f)) { //如果縮小的範圍比允許的最小範圍還要小,就重置縮放因子為當前的狀態的因子 if (scale * scaleFactor < minScale && scaleFactor < 1.0f) { scaleFactor = minScale / scale; } //如果縮小的範圍比允許的最小範圍還要小,就重置縮放因子為當前的狀態的因子 if (scale * scaleFactor > maxScale && scaleFactor > 1.0f) { scaleFactor = maxScale / scale; } //scaleMatrix.postScale(1.0f, 1.0f, getWidth() / 2, getHeight() / 2); scaleMatrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); checkBoderAndCenter();//處理縮放後圖片邊界與螢幕有間隙或者不居中的問題 setImageMatrix(scaleMatrix);//千萬不要忘記設定這個,我總是忘記 } return true; } /** * 實現雙擊放大或者縮小圖片。用到的知識點就是GestureDetector,用它來監測雙擊事件。 * 至於雙擊後怎麼縮放圖片,相信在前面幾篇文章中,你都已經很熟悉了。 * 但是難點是,我們要求雙擊後緩慢的放大或者縮小,而不是一下子就放大到或者縮小到目標值。 * 這裡就要結合線程來處理了。其實處理的邏輯也很簡單: * 比如說放大,我們每隔一段時間,就對圖片進行放大一次,然後看看是不是達到要求的放大比例了, * 如果達到了就終止,否則繼續放大,直到達到要求為止。 */ private class MySimpleOnGestureListener extends GestureDetector.SimpleOnGestureListener { public boolean onDoubleTap(MotionEvent e) { //如果正在執行雙擊縮放,直接跳過 if (isAutoScale) { return true; } float x = e.getX(); float y = e.getY(); //獲得當前的縮放比例 float scale = getDrawableScale(); //如果比midScale小,一律放大,否則一律縮小為initScale if (scale < midScale) { // scaleMatrix.postScale(midScale/scale,midScale/scale, x, y); // setImageMatrix(scaleMatrix); postDelayed(new AutoScaleRunnable(midScale, x, y), 16); isAutoScale = true; } else { // scaleMatrix.postScale(initScale/scale,initScale/scale, x, y); // setImageMatrix(scaleMatrix); postDelayed(new AutoScaleRunnable(initScale, x, y), 16); isAutoScale = true; } return true; } } /** * 將 雙擊縮放使用梯度 * * @author fuly1314 */ private class AutoScaleRunnable implements Runnable { private float targetScale;//縮放的目標值 private float x; private float y;//縮放的中心點 private float tempScale; private float BIGGER = 1.07F; private float SMALL = 0.93F;//縮放的梯度 public AutoScaleRunnable(float targetScale, float x, float y) { super(); this.targetScale = targetScale; this.x = x; this.y = y; if (getDrawableScale() < targetScale) { tempScale = BIGGER; } if (getDrawableScale() > targetScale) { tempScale = SMALL; } } public void run() { scaleMatrix.postScale(tempScale, tempScale, x, y); checkBoderAndCenter(); setImageMatrix(scaleMatrix); float scale = getDrawableScale(); if ((scale < targetScale && tempScale > 1.0f) || (scale > targetScale && tempScale < 1.0f)) { postDelayed(this, 16); } else { scaleMatrix.postScale(targetScale / scale, targetScale / scale, x, y); checkBoderAndCenter(); setImageMatrix(scaleMatrix); isAutoScale = false; } } } /** * 處理縮放後圖片邊界與螢幕有間隙或者不居中的問題 */ private void checkBoderAndCenter() { RectF rectf = getDrawableRectF(); int width = getWidth(); int height = getHeight(); float delaX = 0; float delaY = 0; if (rectf.width() >= width) { if (rectf.left > 0) { delaX = -rectf.left; } if (rectf.right < width) { delaX = width - rectf.right; } } if (rectf.height() >= height) { if (rectf.top > 0) { delaY = -rectf.top; } if (rectf.bottom < height) { delaY = height - rectf.bottom; } } if (rectf.width() < width) { delaX = width / 2 - rectf.right + rectf.width() / 2; } if (rectf.height() < height) { delaY = height / 2 - rectf.bottom + rectf.height() / 2; } scaleMatrix.postTranslate(delaX, delaY); } /** * 獲取圖片根據矩陣變換後的四個角的座標,即left,top,right,bottom * * @return */ private RectF getDrawableRectF() { Matrix matrix = scaleMatrix; RectF rectf = new RectF(); Drawable d = getDrawable(); if (d != null) { rectf.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); } matrix.mapRect(rectf); return rectf; } /** * 監聽觸控事件 */ public boolean onTouch(View v, MotionEvent event) { if (mGestureDetector != null && mGestureDetector.onTouchEvent(event)) { return true; } if (mScaleGestureDetector != null) { //將觸控事件傳遞給手勢縮放這個類 mScaleGestureDetector.onTouchEvent(event); } //獲得多點個數,也叫螢幕上手指的個數 int pointCount = event.getPointerCount(); float x = 0; float y = 0;//中心點的x和y for (int i = 0; i < pointCount; i++) { x += event.getX(i); y += event.getY(i); } //求出中心點的位置 x /= pointCount; y /= pointCount; //如果手指的數量發生了改變,則不移動 if (mLastPoint != pointCount) { isCanDrag = false; mLastX = x; mLastY = y; } mLastPoint = pointCount; RectF rectf = getDrawableRectF(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (rectf.width() > getWidth() + 0.01 || rectf.height() > getHeight() + 0.01) { //請求父類不要攔截ACTION_DOWN事件 if (getParent() instanceof ViewPager) this.getParent().requestDisallowInterceptTouchEvent(true); } break; case MotionEvent.ACTION_MOVE: if (rectf.width() > getWidth() + 0.01 || rectf.height() > getHeight() + 0.01) { //請求父類不要攔截ACTION_MOVE事件 if (getParent() instanceof ViewPager) this.getParent().requestDisallowInterceptTouchEvent(true); } //求出移動的距離 float dx = x - mLastX; float dy = y - mLastY; if (!isCanDrag) { isCanDrag = isCanDrag(dx, dy); } if (isCanDrag) { //如果圖片能正常顯示,就不需要移動了 if (rectf.width() <= getWidth()) { dx = 0; } if (rectf.height() <= getHeight()) { dy = 0; } //開始移動 scaleMatrix.postTranslate(dx, dy); //處理移動後圖片邊界與螢幕有間隙或者不居中的問題 checkBoderAndCenterWhenMove(); setImageMatrix(scaleMatrix); } mLastX = x; mLastY = y; break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mLastPoint = 0; break; } return true; } /** * 處理移動後圖片邊界與螢幕有間隙或者不居中的問題 * 這跟我們前面寫的程式碼很像 */ private void checkBoderAndCenterWhenMove() { RectF rectf = getDrawableRectF(); float delaX = 0; float delaY = 0; int width = getWidth(); int height = getHeight(); if (rectf.width() > width && rectf.left > 0) { delaX = -rectf.left; } if (rectf.width() > width && rectf.right < width) { delaX = width - rectf.right; } if (rectf.height() > height && rectf.top > 0) { delaY = -rectf.top; } if (rectf.height() > height && rectf.bottom < height) { delaY = height - rectf.bottom; } scaleMatrix.postTranslate(delaX, delaY); } /** * 判斷是否觸發移動效果 * * @param dx * @param dy * @return */ private boolean isCanDrag(float dx, float dy) { return Math.sqrt(dx * dx + dy * dy) > mScaleSlop; } }
我們不生產程式碼,我們是網際網路的搬運工,感謝作者:https://www.cnblogs.com/fuly550871915/