您好,登錄后才能下訂單哦!
我們就把這個問題叫做圖片查看器吧,它的主要功能有:
1、雙擊縮放圖片。
2、 雙指縮放圖片。
3、單指拖拽圖片。
為此這個圖片查看器需要考慮以下的技術點:
一、雙擊縮放圖片:
1、如果圖片高度比屏幕的高度小得多,那么就將圖片放大到高度與屏幕高度相等,否則就放大一個特定的倍數。
2、如何判斷是否到達這個倍數來停止縮放。
3、判斷完且停止放大后,圖片可能已經超出了這個倍數需要的大小,如何回歸到我們的目標大小。
4、判斷完且停止縮小后,圖片寬度可能已經小于屏幕寬度,在兩邊留下了空白,如何重置為原來的大小。
二、雙指縮放圖片:
1、雙指縮放,放大一個特定的倍數停止。
2、如何判斷是否到達這個倍數。
3、放大停止后,圖片可能已經超出了這個倍數需要的大小,如何回歸到我們的目標大小。
4、縮小停止后,圖片寬度可能已經小于屏幕寬度,在兩邊留下了空白,如何重置為原來的大小。
三、單指拖拽:
1、當圖片寬度小于或等于屏幕寬度的時候,禁止左右移動,當圖片的高度小于屏幕高度的時候,禁止上下移動。
2、移動圖片時,如果圖片的一邊已經與屏幕之間有了空白,松手后恢復,讓圖片的這一邊與屏幕邊界重合。
四、
如何判斷是雙擊,還是多指觸控,還是單指。
五、
如何解決與viewPager的滑動沖突,當圖片已經滑動到盡頭無法滑動時,此時viewPager應該攔截事件。
我們逐一來解決:
public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener { public MyImageView(Context context, AttributeSet attrs) { super(context, attrs); super.setScaleType(ScaleType.MATRIX); setOnTouchListener(this); /** * 雙擊實現圖片放大縮小 */ mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { changeViewSize(e); return true; } }); }
在這里縮放圖片是用matrix,因此首先要設置scaleType為matrix。
用手勢判斷雙擊行為。不要忘了在onTouch里面加上
if (mGestureDetector.onTouchEvent(event)) return true;
判斷單指與多指觸控,則在onTouch里面判斷,要用 event.getAction() & MotionEvent.ACTION_MASK來判斷。
//多指觸控模式,單指,雙指 private int mode; private final static int SINGLE_TOUCH = 1; //單指 private final static int DOUBLE_TOUCH = 2; //雙指 @Override public boolean onTouch(View view, MotionEvent event) { rectF = getMatrixRectF(); if (mGestureDetector.onTouchEvent(event)) return true; switch (event.getAction() & event.getActionMasked()) { case MotionEvent.ACTION_DOWN: mode = SINGLE_TOUCH; break; case MotionEvent.ACTION_MOVE: if (mode >= DOUBLE_TOUCH) //雙指縮放 { } if (mode == SINGLE_TOUCH) //單指拖拽 { } break; case MotionEvent.ACTION_POINTER_DOWN: mode += 1;break; case MotionEvent.ACTION_POINTER_UP: mode -= 1; break; case MotionEvent.ACTION_UP: mode = 0; break; //在ACTION_MOVE中,事件被攔截了之后,有時候ACTION_UP無法觸發,所以加上了ACTION_CANCEL case MotionEvent.ACTION_CANCEL: mode = 0; break; default: break; } return true; }
有如下事件使我們要用到的:
MotionEvent.ACTION_DOWN:在第一個點被按下時觸發
MotionEvent.ACTION_UP:當屏幕上唯一的點被放開時觸發
MotionEvent.ACTION_POINTER_DOWN:當屏幕上已經有一個點被按住,此時再按下其他點時觸發。
MotionEvent.ACTION_POINTER_UP:當屏幕上有多個點被按住,松開其中一個點時觸發(即非最后一個點被放開時)。
MotionEvent.ACTION_MOVE:當有點在屏幕上移動時觸發。值得注意的是,由于它的靈敏度很高,而我們的手指又不可能完全靜止(即使我們感覺不到移動,但其實我們的手指也在不停地抖動),所以實際的情況是,基本上只要有點在屏幕上,此事件就會一直不停地被觸發。
在ACTION_MOVE中通過mode的大小來判斷是單指還是雙指。
不過有一個令人傷心的事情,Android自己有一個bug。經過測試發現雙指交換觸碰圖片的時候,程序會閃退,出現異常:pointIndex out of range。這是Android自己的bug。個人覺得最好得解決方法是自定義一個viewPager,然后在里面重寫:onTouchEvent,onInterceptTouchEvent,然后捕獲異常。
@Override public boolean onTouchEvent(MotionEvent ev) { try { return super.onTouchEvent(ev); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } return false; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { try { return super.onInterceptTouchEvent(ev); } catch (IllegalArgumentException ex) { ex.printStackTrace(); } return false; }
這樣程序就不會閃退了。
我們來看看雙擊放大的的代碼:
/** * 雙擊縮放圖片 */ private void changeViewSize(MotionEvent e) { //獲取雙擊的坐標 final float x = e.getX(); final float y = e.getY(); //如果此時還在縮放那就直接返回 if (animator != null && animator.isRunning()) return; //判斷是處于放大還是縮小的狀態 if (!isZoomChanged()) { animator = ValueAnimator.ofFloat(1.0f, 2.0f); } else { animator = ValueAnimator.ofFloat(1.0f, 0.0f); } animator.setTarget(this); animator.setDuration(500); animator.setInterpolator(new DecelerateInterpolator()); animator.start(); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { Float value = (Float) animator.getAnimatedValue(); matrix.postScale(value, value, x, y); checkBorderAndCenterWhenScale(); //在縮放后讓圖片居中 setImageMatrix(matrix); /** * 控制縮小的范圍 * 如果已經小于初始大小,那么恢復到初始大小,然后停止 */ if (checkRestScale()) { matrix.set(oldMatrix); //oldMatrix為最原始的matrix setImageMatrix(matrix); return; } /** * 控制放大的范圍 * 如果已經大于目標的放大倍數,那么直接置為目標的放大倍數 * 然后停止 */ if (getMatrixValueX() >= mDoubleClickScale) { matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y); checkBorderAndCenterWhenScale(); setImageMatrix(matrix); return; } } }); }
判斷處于放大還是縮小狀態的代碼:(不是初始值就說明是處于放大狀態)
/** * 判斷縮放級別是否是改變過 * * @return true表示非初始值, false表示初始值 */ private boolean isZoomChanged() { float[] values = new float[9]; getImageMatrix().getValues(values); //獲取當前X軸縮放級別 float scale = values[Matrix.MSCALE_X]; //獲取初始時候的X軸縮放級別,兩者做比較 oldMatrix.getValues(values); return scale != values[Matrix.MSCALE_X]; }
getMatrixValue()的代碼如下,是為了取得當前的放大倍數,相對于一開始的圖片來說
private float getMatrixValueX() { // TODO Auto-generated method stub float[] values = new float[9]; getImageMatrix().getValues(values); //獲取當前X軸縮放級別 float scale = values[Matrix.MSCALE_X]; //獲取原始Matrix的X軸縮放級別 oldMatrix.getValues(values); //返回放大的倍數 return scale / values[Matrix.MSCALE_X]; }
checkRestScale()的代碼如下,主要是為了判斷當前的縮放級別是否小于最初始的縮放級別。
/** * 判斷是否需要重置 * * @return 當前縮放級別小于原始縮放級別時,重置 */ private boolean checkRestScale() { // TODO Auto-generated method stub float[] values = new float[9]; getImageMatrix().getValues(values); //獲取當前X軸縮放級別 float scale = values[Matrix.MSCALE_X]; //獲取原始的X軸縮放級別,兩者做比較 oldMatrix.getValues(values); return scale < values[Matrix.MSCALE_X]; }
checkBorderAndCenterWhenScale()的代碼如下,否則圖片縮放后位置會發生變化。
/** * 在縮放時,進行圖片顯示范圍的控制 */ private void checkBorderAndCenterWhenScale() { RectF rect = getMatrixRectF(); float deltaX = 0; float deltaY = 0; int width = getWidth(); int height = getHeight(); // 如果寬或高大于屏幕,則控制范圍 if (rect.width() >= width) { if (rect.left > 0) { deltaX = -rect.left; } if (rect.right < width) { deltaX = width - rect.right; } } if (rect.height() >= height) { if (rect.top > 0) { deltaY = -rect.top; } if (rect.bottom < height) { deltaY = height - rect.bottom; } } // 如果寬或高小于屏幕,則讓其居中 if (rect.width() < width) { deltaX = width * 0.5f - rect.right + 0.5f * rect.width(); } if (rect.height() < height) { deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height(); } matrix.postTranslate(deltaX, deltaY); setImageMatrix(matrix); }
接下看看雙指縮放和單指拖拽:
@Override public boolean onTouch(View view, MotionEvent event) { rectF = getMatrixRectF(); //獲取圖片邊界范圍 if (mGestureDetector.onTouchEvent(event)) return true; switch (event.getAction() & event.getActionMasked()) { case MotionEvent.ACTION_DOWN: //如果放大后圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理 if (rectF.width() > getWidth() || rectF.height() > getHeight()) { getParent().requestDisallowInterceptTouchEvent(true); } mode = SINGLE_TOUCH; x = (int) event.getRawX(); y = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (mode >= DOUBLE_TOUCH) //雙指縮放 { getParent().requestDisallowInterceptTouchEvent(true); newDist = calculateDist(event); //計算距離 Point point = getMiPoint(event); //獲取兩手指間的中點坐標 if (newDist > oldDist + 1) //放大(加一是為了防止抖動) { changeViewSize(oldDist, newDist, point); //根據距離實現放大縮小 oldDist = newDist; } if (oldDist > newDist + 1) //縮小 { changeViewSize(oldDist, newDist, point); oldDist = newDist; } } if (mode == SINGLE_TOUCH) //單指拖拽 { float dx = event.getRawX() - x; float dy = event.getRawY() - y; //如果移動過程中圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理 if (rectF.width() > getWidth() || rectF.height() > getHeight()) { getParent().requestDisallowInterceptTouchEvent(true); } //如果向右移動圖片到了盡頭,那么就不要攔截事件,讓viewPager處理 if (rectF.left >= 0 && dx > 0) getParent().requestDisallowInterceptTouchEvent(false); //如果向左移動到了盡頭,那么就不要攔截事件,讓viewPager處理 if (rectF.right <= getWidth() && dx < 0) getParent().requestDisallowInterceptTouchEvent(false); if (getDrawable() != null) { //如果圖片寬度或高度沒有超出屏幕,那么就禁止左右或上下滑動 if (rectF.width() <= getWidth()) dx = 0; if (rectF.height() < getHeight()) dy = 0; //如果圖片向下移動到了盡頭,不讓它繼續移動 if (rectF.top >= 0 && dy > 0) dy = 0; //如果圖片向上移動到了盡頭,不讓它繼續移動 if (rectF.bottom <= getHeight() && dy < 0) dy = 0; //當移動距離大于1的時候再移動,因為ACTION_MOVE比較靈敏, // 手指即使只是放在上面,依然能夠檢測到手指的抖動,然后讓圖片移動。 if (Math.abs(dx) > 1 || Math.abs(dy) > 1) matrix.postTranslate(dx, dy); setImageMatrix(matrix); } } x = (int) event.getRawX(); y = (int) event.getRawY(); break; case MotionEvent.ACTION_POINTER_DOWN: mode += 1; oldDist = calculateDist(event); break; case MotionEvent.ACTION_POINTER_UP: mode -= 1; break; case MotionEvent.ACTION_UP: backToPosition(); mode = 0; break; //在ACTION_MOVE中,事件被攔截了之后,有時候ACTION_UP無法觸發,所以加上了ACTION_CANCEL case MotionEvent.ACTION_CANCEL: backToPosition(); mode = 0; break; default: break; } return true; }
首先先來看一個方法,根據圖片的matrix獲得圖片的邊界范圍,這個范圍映射在rect上。(這個范圍檢測是用在單指拖拽上的)
/** * 根據當前圖片的Matrix獲得圖片的范圍 * * @return */ private RectF getMatrixRectF() { RectF rect = new RectF(); Drawable d = getDrawable(); if (null != d) { rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); matrix.mapRect(rect); } Log.e("aaaa",""+rect.bottom+" "+rect.left+" "+rect.right+" "+rect.top); return rect; }
rect.bottom:圖片下邊界的縱坐標
rect.left:圖片左邊界的橫坐標
rect.right:圖片右邊界的橫坐標
rect.top:圖片上邊界的縱坐標
rectF.width():圖片寬度
rectF.height():圖片高度
需要注意的是Matrix對圖片的操作都是操作ImageView里面的bitmap,ImageView是沒有變化的,上面所說的屏幕邊界其實ImageView的邊界,getWidth(),getHeight()是ImageView的寬和高。
方法 backToPosition()主要是實現單指拖拽的技術點2,當手指快速劃過去的時候,在檢測到無法繼續滑動前圖片邊界與屏幕邊界已經出現了距離,所以松開手指的時候要復位,讓圖片邊界與屏幕邊界重合。
/** * 若是在移動后圖片的邊界脫離屏幕邊界,那么就讓圖片邊界與屏幕邊界重合 * 若手指快速移動,停止后會出現圖片距離屏幕有一段空白距離,然后經過判斷不能再移動, * 但是在進行下一次判斷是否可以繼續移動之前就已經出現了。 * 所以需要復位 */ private void backToPosition() { if (rectF.left >= 0) { //圖片左邊界與屏幕出現距離 matrix.postTranslate(-rectF.left, 0); setImageMatrix(matrix); } if (rectF.right <= getWidth()) { //圖片右邊界與屏幕出現距離 matrix.postTranslate(getWidth() - rectF.right, 0); setImageMatrix(matrix); } if (rectF.top >= 0) { //圖片上邊界與屏幕出現距離 matrix.postTranslate(0, -rectF.top); setImageMatrix(matrix); } if (rectF.bottom <= getHeight()) { //圖片下邊界與屏幕出現距離 matrix.postTranslate(0, getHeight() - rectF.bottom); setImageMatrix(matrix); } }
獲取兩手指間的中點坐標
/** * 獲取雙指縮放時候的縮放中點 * * @return */ private Point getMiPoint(MotionEvent event) { float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); mPoint.set((int) x / 2, (int) y / 2); return mPoint; }
計算兩指觸摸點的距離
/** * 計算兩指觸摸點之間的距離 */ private float calculateDist(MotionEvent event) { float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return (float) Math.sqrt(x * x + y * y); }
雙指縮放圖片
/** * 雙指縮放圖片 */ private void changeViewSize(float oldDist, float newDist, Point mPoint) { float scale = newDist / oldDist; //縮放比例 matrix.postScale(scale, scale, mPoint.x, mPoint.y); checkBorderAndCenterWhenScale(); setImageMatrix(matrix); //防止縮小的時候小于初始的圖片大小,需要重置 reSetMatrix(); //如果縮放已經大于目標倍數,停止,因為有可能已經超出,那么就直接縮放到目標大小 if (getMatrixValueX() >= MAX_SCALE) { matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y); checkBorderAndCenterWhenScale(); setImageMatrix(matrix); return; } }
reSetMatrix()的代碼如下:
/** * 重置Matrix */ private void reSetMatrix() { if (checkRestScale()) { matrix.set(oldMatrix); setImageMatrix(matrix); return; } }
checkRestScale()的代碼在上面已經給出了。oldMatrix為最初始的Matrix。
到這里還沒有結束,設置Imageview的ScaleType為Matrix,那么圖片不會主動縮放到適應屏幕,也不會處于屏幕中間,因此我們的自定義ImageView需要繼承ViewTreeObserver.OnGlobalLayoutListener
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); }
@Override public void onGlobalLayout() { if (once) { Drawable d = getDrawable(); if (d == null) return; Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight()); int width = getWidth(); int height = getHeight(); // 拿到圖片的寬和高 int dw = d.getIntrinsicWidth(); int dh = d.getIntrinsicHeight(); float scale = 1.0f; // 如果圖片的寬或者高大于屏幕,則縮放至屏幕的寬或者高 if (dw > width && dh <= height) { scale = width * 1.0f / dw; } if (dh > height && dw <= width) { scale = height * 1.0f / dh; } // 如果寬和高都大于屏幕,則讓其按按比例適應屏幕大小 if (dw > width && dh > height) { scale = Math.min(width * 1.0f / dw, height * 1.0f / dh); } initScale = scale; Log.e("TAG", "initScale = " + initScale); matrix.postTranslate((width - dw) / 2, (height - dh) / 2); matrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2); // 圖片移動至屏幕中心 setImageMatrix(matrix); oldMatrix.set(getImageMatrix()); once = false; RectF rectF=getMatrixRectF(); setDoubleClickScale(rectF); } } // 拿到圖片的寬和高 int dw = d.getIntrinsicWidth(); int dh = d.getIntrinsicHeight();
拿到的圖片寬和高是bitmap的真實高度。
初始的oldMatrix就是在這里設置的,然后作為初始模板,代表著圖片沒被動手改變的Matrix。至于方法 setDoubleClickScale(rectF);只是設置雙擊放大的倍數而已,如果圖片高度比屏幕的高度小得多,那么就將圖片放大到高度與屏幕高度相等,否則就放大一個特定的倍數。必須在這里設置,因為在這里取到的rectF才能反映原始圖片的邊界,因為這時候還沒有動手改變圖片。
/** * 設置雙擊放大的倍數 */ private void setDoubleClickScale(RectF rectF) { if(rectF.height()<getHeight()-100) { mDoubleClickScale=getHeight()/rectF.height(); } else mDoubleClickScale=2f; }
到這里大概結束了,下面就貼出完整的代碼:
package com.example.tangzh.myimageview; import android.animation.ValueAnimator; import android.content.Context; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.view.View; import android.view.ViewTreeObserver; import android.view.animation.DecelerateInterpolator; import android.widget.ImageView; /** * Created by TangZH on 2017/5/3. */ public class MyImageView extends ImageView implements ViewTreeObserver.OnGlobalLayoutListener,View.OnTouchListener { private final static int SINGLE_TOUCH = 1; //單指 private final static int DOUBLE_TOUCH = 2; //雙指 //多指觸控模式,單指,雙指 private int mode; //兩指觸碰點之間的距離 private float oldDist; private float newDist; /** * 最大縮放級別 */ private static final float MAX_SCALE = 5f; /** * 雙擊時的縮放級別 */ private float mDoubleClickScale = 2; /** * 初始化時的縮放比例,如果圖片寬或高大于屏幕,此值將小于0 */ private float initScale = 1.0f; private boolean once = true; private RectF rectF; /** * 用于雙擊檢測 */ private GestureDetector mGestureDetector; private int x = 0; private int y = 0; private Point mPoint = new Point(); private final Matrix matrix = new Matrix(); private Matrix oldMatrix = new Matrix(); private ValueAnimator animator; public MyImageView(Context context) { this(context, null); } public MyImageView(Context context, AttributeSet attrs) { super(context, attrs); super.setScaleType(ScaleType.MATRIX); setOnTouchListener(this); /** * 雙擊實現圖片放大縮小 */ mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onDoubleTap(MotionEvent e) { changeViewSize(e); return true; } }); } @Override public boolean onTouch(View view, MotionEvent event) { rectF = getMatrixRectF(); //獲取圖片邊界范圍 if (mGestureDetector.onTouchEvent(event)) return true; switch (event.getAction() & event.getActionMasked()) { case MotionEvent.ACTION_DOWN: //如果放大后圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理 if (rectF.width() > getWidth() || rectF.height() > getHeight()) { getParent().requestDisallowInterceptTouchEvent(true); } mode = SINGLE_TOUCH; x = (int) event.getRawX(); y = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: if (mode >= DOUBLE_TOUCH) //雙指縮放 { getParent().requestDisallowInterceptTouchEvent(true); newDist = calculateDist(event); //計算距離 Point point = getMiPoint(event); //獲取兩手指間的中點坐標 if (newDist > oldDist + 1) //放大(加一是為了防止抖動) { changeViewSize(oldDist, newDist, point); //根據距離實現放大縮小 oldDist = newDist; } if (oldDist > newDist + 1) //縮小 { changeViewSize(oldDist, newDist, point); oldDist = newDist; } } if (mode == SINGLE_TOUCH) //單指拖拽 { float dx = event.getRawX() - x; float dy = event.getRawY() - y; //如果移動過程中圖片的邊界超出了屏幕,那么就攔截事件,不讓viewPager處理 if (rectF.width() > getWidth() || rectF.height() > getHeight()) { getParent().requestDisallowInterceptTouchEvent(true); } //如果向右移動圖片到了盡頭,那么就不要攔截事件,讓viewPager處理 if (rectF.left >= 0 && dx > 0) getParent().requestDisallowInterceptTouchEvent(false); //如果向左移動到了盡頭,那么就不要攔截事件,讓viewPager處理 if (rectF.right <= getWidth() && dx < 0) getParent().requestDisallowInterceptTouchEvent(false); if (getDrawable() != null) { //如果圖片寬度或高度沒有超出屏幕,那么就禁止左右或上下滑動 if (rectF.width() <= getWidth()) dx = 0; if (rectF.height() < getHeight()) dy = 0; //如果圖片向下移動到了盡頭,不讓它繼續移動 if (rectF.top >= 0 && dy > 0) dy = 0; //如果圖片向上移動到了盡頭,不讓它繼續移動 if (rectF.bottom <= getHeight() && dy < 0) dy = 0; //當移動距離大于1的時候再移動,因為ACTION_MOVE比較靈敏, // 手指即使只是放在上面,依然能夠檢測到手指的抖動,然后讓圖片移動。 if (Math.abs(dx) > 1 || Math.abs(dy) > 1) matrix.postTranslate(dx, dy); setImageMatrix(matrix); } } x = (int) event.getRawX(); y = (int) event.getRawY(); break; case MotionEvent.ACTION_POINTER_DOWN: mode += 1; oldDist = calculateDist(event); Log.e("q", "" + "a"); Log.e(":::", "" + event.getPointerCount() + " " + event.getActionIndex() + " " + event.findPointerIndex(0)); break; case MotionEvent.ACTION_POINTER_UP: mode -= 1; break; case MotionEvent.ACTION_UP: backToPosition(); mode = 0; break; //在ACTION_MOVE中,事件被攔截了之后,有時候ACTION_UP無法觸發,所以加上了ACTION_CANCEL case MotionEvent.ACTION_CANCEL: backToPosition(); mode = 0; break; default: break; } return true; } /** * 計算兩指觸摸點之間的距離 */ private float calculateDist(MotionEvent event) { float x = event.getX(0) - event.getX(1); float y = event.getY(0) - event.getY(1); return (float) Math.sqrt(x * x + y * y); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this); } /** * 若是在移動后圖片的邊界脫離屏幕邊界,那么就讓圖片邊界與屏幕邊界重合 * 若手指快速移動,停止后會出現圖片距離屏幕有一段空白距離,然后經過判斷不能再移動, * 但是在進行下一次判斷是否可以繼續移動之前就已經出現了。 * 所以需要復位 */ private void backToPosition() { if (rectF.left >= 0) { //圖片左邊界與屏幕出現距離 matrix.postTranslate(-rectF.left, 0); setImageMatrix(matrix); } if (rectF.right <= getWidth()) { //圖片右邊界與屏幕出現距離 matrix.postTranslate(getWidth() - rectF.right, 0); setImageMatrix(matrix); } if (rectF.top >= 0) { //圖片上邊界與屏幕出現距離 matrix.postTranslate(0, -rectF.top); setImageMatrix(matrix); } if (rectF.bottom <= getHeight()) { //圖片下邊界與屏幕出現距離 matrix.postTranslate(0, getHeight() - rectF.bottom); setImageMatrix(matrix); } } /** * 獲取雙指縮放時候的縮放中點 * * @return */ private Point getMiPoint(MotionEvent event) { float x = event.getX(0) + event.getX(1); float y = event.getY(0) + event.getY(1); mPoint.set((int) x / 2, (int) y / 2); return mPoint; } /** * 雙指縮放圖片 */ private void changeViewSize(float oldDist, float newDist, Point mPoint) { float scale = newDist / oldDist; //縮放比例 matrix.postScale(scale, scale, mPoint.x, mPoint.y); checkBorderAndCenterWhenScale(); setImageMatrix(matrix); //防止縮小的時候小于初始的圖片大小,需要重置 reSetMatrix(); //如果縮放已經大于目標倍數,停止,因為有可能已經超出,那么就直接縮放到目標大小 if (getMatrixValueX() >= MAX_SCALE) { matrix.postScale(MAX_SCALE/getMatrixValueX(), MAX_SCALE/getMatrixValueX(), x, y); checkBorderAndCenterWhenScale(); setImageMatrix(matrix); return; } } /** * 雙擊縮放圖片 */ private void changeViewSize(MotionEvent e) { //獲取雙擊的坐標 final float x = e.getX(); final float y = e.getY(); //如果此時還在縮放那就直接返回 if (animator != null && animator.isRunning()) return; //判斷是處于放大還是縮小的狀態 if (!isZoomChanged()) { animator = ValueAnimator.ofFloat(1.0f, 2.0f); } else { animator = ValueAnimator.ofFloat(1.0f, 0.0f); } animator.setTarget(this); animator.setDuration(500); animator.setInterpolator(new DecelerateInterpolator()); animator.start(); animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { Float value = (Float) animator.getAnimatedValue(); matrix.postScale(value, value, x, y); checkBorderAndCenterWhenScale(); setImageMatrix(matrix); /** * 控制縮小的范圍 * 如果已經小于初始大小,那么恢復到初始大小,然后停止 */ if (checkRestScale()) { matrix.set(oldMatrix); setImageMatrix(matrix); return; } /** * 控制放大的范圍 * 如果已經大于目標的放大倍數,那么直接置為目標的放大倍數 * 然后停止 */ if (getMatrixValueX() >= mDoubleClickScale) { matrix.postScale(mDoubleClickScale/getMatrixValueX(), mDoubleClickScale/getMatrixValueX(), x, y); checkBorderAndCenterWhenScale(); setImageMatrix(matrix); return; } } }); } /** * 判斷縮放級別是否是改變過 * * @return true表示非初始值, false表示初始值 */ private boolean isZoomChanged() { float[] values = new float[9]; getImageMatrix().getValues(values); //獲取當前X軸縮放級別 float scale = values[Matrix.MSCALE_X]; //獲取模板的X軸縮放級別,兩者做比較 oldMatrix.getValues(values); return scale != values[Matrix.MSCALE_X]; } /** * 重置Matrix */ private void reSetMatrix() { if (checkRestScale()) { matrix.set(oldMatrix); setImageMatrix(matrix); return; } } /** * 設置雙擊放大的倍數 */ private void setDoubleClickScale(RectF rectF) { if(rectF.height()<getHeight()-100) { mDoubleClickScale=getHeight()/rectF.height(); } else mDoubleClickScale=2f; } /** * 判斷是否需要重置 * * @return 當前縮放級別小于模板縮放級別時,重置 */ private boolean checkRestScale() { // TODO Auto-generated method stub float[] values = new float[9]; getImageMatrix().getValues(values); //獲取當前X軸縮放級別 float scale = values[Matrix.MSCALE_X]; //獲取模板的X軸縮放級別,兩者做比較 oldMatrix.getValues(values); return scale < values[Matrix.MSCALE_X]; } private float getMatrixValueX() { // TODO Auto-generated method stub float[] values = new float[9]; getImageMatrix().getValues(values); //獲取當前X軸縮放級別 float scale = values[Matrix.MSCALE_X]; //獲取模板的X軸縮放級別,兩者做比較 oldMatrix.getValues(values); return scale / values[Matrix.MSCALE_X]; } /** * 在縮放時,進行圖片顯示范圍的控制 */ private void checkBorderAndCenterWhenScale() { RectF rect = getMatrixRectF(); float deltaX = 0; float deltaY = 0; int width = getWidth(); int height = getHeight(); // 如果寬或高大于屏幕,則控制范圍 if (rect.width() >= width) { if (rect.left > 0) { deltaX = -rect.left; } if (rect.right < width) { deltaX = width - rect.right; } } if (rect.height() >= height) { if (rect.top > 0) { deltaY = -rect.top; } if (rect.bottom < height) { deltaY = height - rect.bottom; } } // 如果寬或高小于屏幕,則讓其居中 if (rect.width() < width) { deltaX = width * 0.5f - rect.right + 0.5f * rect.width(); } if (rect.height() < height) { deltaY = height * 0.5f - rect.bottom + 0.5f * rect.height(); } Log.e("TAG", "deltaX = " + deltaX + " , deltaY = " + deltaY); matrix.postTranslate(deltaX, deltaY); setImageMatrix(matrix); } /** * 根據當前圖片的Matrix獲得圖片的范圍 * * @return */ private RectF getMatrixRectF() { RectF rect = new RectF(); Drawable d = getDrawable(); if (null != d) { rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); matrix.mapRect(rect); //如果沒有這個,那么下面Log的輸出將會與上一句的一樣。 } Log.e("aaaa",""+rect.bottom+" "+rect.left+" "+rect.right+" "+rect.top); return rect; } @Override public void onGlobalLayout() { if (once) { Drawable d = getDrawable(); if (d == null) return; Log.e("TAG", d.getIntrinsicWidth() + " , " + d.getIntrinsicHeight()); int width = getWidth(); int height = getHeight(); // 拿到圖片的寬和高 int dw = d.getIntrinsicWidth(); int dh = d.getIntrinsicHeight(); float scale = 1.0f; // 如果圖片的寬或者高大于屏幕,則縮放至屏幕的寬或者高 if (dw > width && dh <= height) { scale = width * 1.0f / dw; } if (dh > height && dw <= width) { scale = height * 1.0f / dh; } // 如果寬和高都大于屏幕,則讓其按按比例適應屏幕大小 if (dw > width && dh > height) { scale = Math.min(width * 1.0f / dw, height * 1.0f / dh); } initScale = scale; Log.e("TAG", "initScale = " + initScale); matrix.postTranslate((width - dw) / 2, (height - dh) / 2); matrix.postScale(scale, scale, getWidth() / 2, getHeight() / 2); // 圖片移動至屏幕中心 setImageMatrix(matrix); oldMatrix.set(getImageMatrix()); once = false; RectF rectF=getMatrixRectF(); setDoubleClickScale(rectF); } } }
唉,雖然已經寫完了,但是還有一個問題沒有解決,就是移動圖片到盡頭,這時候不要放手,往反方向移動,就會出現一個問題,圖片反方向的部分被遮擋,無法看到,然后移動的時候是直接切換圖片,而不再繼續移動圖片,這個問題的原因是:當你移動圖片到盡頭時,就把事件交給viewpager來處理了,即使再往反方向移動圖片,viewPager也一樣繼續攔截了事件。目前沒解決。
以上所述是小編給大家介紹的在viewPager中雙指縮放圖片雙擊縮放圖片單指拖拽圖片的實現思路,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。