android實現控制元件的手勢縮放、移動以及雙擊還原
我分四部分介紹:
1.Imageview利用Matrix和OnScaleGestureListener實現手勢縮放;
2.在第一部分的基礎上實現圖片跟隨手指進行滑動;
3.在一、二的基礎上利用GestureDetector的OnDoubleTap回撥實現圖片雙擊還原;
4.在一、二、三的基礎上將ImageView擴充套件為FrameLayout,實現佈局內所有子控制元件都能手勢縮放、移動、雙擊還原。
一、Imageview利用Matrix和OnScaleGestureListener實現手勢縮放
1.基礎知識:
Matrix是一個3*3的矩陣
* MSCALE_X MSKEW_X MTRANS_X * MKEW_Y MSCALE_Y MTRANS_Y * MPERSP_0 MPERSP_1 MPERSP_2
Matrix.postScale(x, y, px, py)矩陣縮放,x是x方向上的縮放大小,y是y方向上的縮放大小,(px,py)是縮放中心
Matrix.postTranslate(dx, dy)矩陣平移
setImageMatrix(matrix)將自己的matrix賦值給ImageView的matrix從而實現ImageView中圖片的縮放、平移等操作
OnScaleGestureListener中的onScale回撥中的ScaleGestureDetector.getScaleFactor()可以獲取當前手勢縮放的大小
ImageView需要設定setScaleType(ScaleType.MATRIX);才能實現縮放
OnScaleGestureListener中的OnScaleBegin回撥必須return true才有縮放效果
2.實現思路:
實現OnScaleGestureListener監聽,在OnScale回撥方法中,根據ScaleGestureDetector.getScaleFactor()獲取當前手勢縮放的大小,利用Matrix.postScale(x, y, px, py)方法將縮放大小儲存在矩陣中,用setImageMatrix(matrix)方法,將矩陣賦值給ImageView的內部矩陣,從而實現圖片的手勢縮放,是不是很簡單。
3.實現程式碼:
/** * 只支援手勢縮放的ImageView */ public class ZoomOnlyImageView extends AppCompatImageView implements ScaleGestureDetector.OnScaleGestureListener { private ScaleGestureDetector scaleGestureDetector;//手勢縮放 /** * MSCALE_X MSKEW_X MTRANS_X * MKEW_Y MSCALE_Y MTRANS_Y * MPERSP_0 MPERSP_1 MPERSP_2 */ private Matrix mMatrix;//縮放矩陣 private float maxScale = 4.0f;//最大縮放到原圖的四倍 private float minScale = 0.5f;//最小縮放到原圖的0.5倍 public ZoomOnlyImageView(Context context) { super(context); init(context); } public ZoomOnlyImageView(Context context, AttributeSet attrs) { super(context, attrs); init(context); } public ZoomOnlyImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context); } //初始化引數 private void init(Context context) { setScaleType(ScaleType.MATRIX);//允許imageview縮放 scaleGestureDetector = new ScaleGestureDetector(new WeakReference<Context>(context).get(), new WeakReference<ZoomOnlyImageView>(this).get()); mMatrix = new Matrix(); } @Override public boolean onScale(ScaleGestureDetector detector) {//OnScaleGestureListener裡的方法 if (getDrawable() == null) { return true; } //獲取本次的縮放值 float scale = detector.getScaleFactor(); Log.i("zhangdi", "scaleFactor = "+scale); float preScale = getPreScale(); Log.i("zhangdi", "preScale = "+preScale); if (preScale * scale < maxScale && preScale * scale > minScale) {//preScale * scale可以計算出此次縮放執行的話,縮放值是多少 //detector.getFocusX()縮放手勢中心的x座標,detector.getFocusY()y座標 // mMatrix.postScale(scale, scale, detector.getFocusX(), detector.getFocusY()); mMatrix.postScale(scale, scale, getWidth()/2, getHeight()/2); setImageMatrix(mMatrix); makeDrawableCenter(); } return true; } @Override public boolean onScaleBegin(ScaleGestureDetector detector) {//OnScaleGestureListener裡的方法,縮放開始 return true;//必須返回true才有效果 } @Override public void onScaleEnd(ScaleGestureDetector detector) {//OnScaleGestureListener裡的方法,縮放結束 } @Override public boolean onTouchEvent(MotionEvent event) { return scaleGestureDetector.onTouchEvent(event); } //獲取目前一共縮放了多少 private float getPreScale() { float[] matrix = new float[9]; mMatrix.getValues(matrix); return matrix[Matrix.MSCALE_X]; } //縮小的時候讓圖片居中 private void makeDrawableCenter() { RectF rect = new RectF(); Drawable d = getDrawable(); if (d != null) { rect.set(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());//設定rect的初始四個角值是圖片的四個頂點值 Log.i("zhangdi", "bitmapWidth = "+d.getIntrinsicWidth()+", bitmapHeight = "+d.getIntrinsicHeight()); mMatrix.mapRect(rect);//獲取通過當前矩陣變換後的四個角值 Log.i("zhangdi", "matrixWidth = "+rect.width()+", matrixHeight = "+rect.height()); Log.i("zhangdi", "bmLeft: "+rect.left+" bmRight: "+rect.right+" bmTop: "+rect.top+" bmBottom: "+rect.bottom); } int width = getWidth(); int height = getHeight(); float dx=0, dy=0; // 如果寬或高大於螢幕,則控制範圍 if (rect.width() >= width) { if (rect.left > 0) { dx = -rect.left; } if (rect.right < width) { dx = width - rect.right; } } if (rect.height() >= height) { if (rect.top > 0) { dy = -rect.top; } if (rect.bottom < height) { dy = height - rect.bottom; } } if (rect.width() < width) { dx = width/2 - (rect.right - rect.width()/2);//控制元件中心點橫座標減去圖片中心點橫座標為X方向應移動距離 } if (rect.height() < height) { dy = height/2 - (rect.bottom - rect.height()/2); } Log.i("zhangdi", "dx = "+dx+", dy = "+dy); if (dx != 0 || dy != 0) { mMatrix.postTranslate(dx, dy); setImageMatrix(mMatrix); } } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); setImageDrawable(null); scaleGestureDetector = null; } }
二、在第一部分的基礎上實現圖片跟隨手指進行滑動
1.實現思路:把每次的手指移動距離賦值給矩陣,將矩陣賦值給ImageView實現圖片的平移,注意控制移動範圍不能超過圖片範圍。
2.程式碼實現:修改第一部分的onTouchEvent方法
@Override public boolean onTouchEvent(MotionEvent event) { scaleGestureDetector.onTouchEvent(event); float x=0, y=0; final int pointerCount = event.getPointerCount();//獲取手指個數 for (int i=0; i<pointerCount; i++) { x += event.getX(i); y += event.getY(i); } x = x/pointerCount;//獲取x平均值 y = y/pointerCount;//獲取y平均值 if (pointerCount != lastPointerCount) { lastX = x; lastY = y; } Log.i("zhangdi", "pointCount: "+pointerCount+", lastPointCount: "+lastPointerCount); lastPointerCount = pointerCount; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = x; lastY = y; break; case MotionEvent.ACTION_MOVE: float delX = x - lastX;//x方向計算移動距離 float delY = y - lastY;//計算y方向移動距離 RectF rectF = getMatrixRectF(); //控制移動邊界不能超出圖片範圍 if ((rectF.left >= 0 && delX > 0) || (rectF.right <= getWidth() && delX < 0)) { delX = 0; } if ((rectF.top >= 0 && delY > 0) || (rectF.bottom <= getHeight() && delY < 0)) { delY = 0; } mMatrix.postTranslate(delX, delY); setImageMatrix(mMatrix); lastX = x; lastY = y; break; case MotionEvent.ACTION_UP: lastPointerCount = 0; break; } return true; }
三、在一、二的基礎上利用GestureDetector的OnDoubleTap回撥實現圖片雙擊還原
1.實現思路:利用GestureDetector的OnDoubleTap監聽回撥,判斷當前矩陣的縮放比例是不是1,不是的話則matrix.reset(),將matrix賦值給ImageView實現圖片雙擊還原。
2.實現程式碼:
1.修改init()方法,建立GestureDetector物件:
//初始化引數 private void init(Context context) { setScaleType(ScaleType.MATRIX);//允許imageview縮放 scaleGestureDetector = new ScaleGestureDetector(new WeakReference<Context>(context).get(), new WeakReference<ZoomTranslateDoubleTapImageView>(this).get()); mMatrix = new Matrix(); gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener(){ @Override public boolean onDoubleTap(MotionEvent e) {//雙擊圖片還原 if (getPreScale() != 1.0f) { ZoomTranslateDoubleTapImageView.this.postDelayed(new Runnable() { @Override public void run() { mMatrix.reset(); setImageMatrix(mMatrix); makeDrawableCenter(); } },16); } return true; } }); }
2.在onTouchEvent方法中增加雙擊事件關聯:
if (gestureDetector.onTouchEvent(event)) { return true; }
四、在一、二、三的基礎上將ImageView擴充套件為FrameLayout,實現佈局內所有子控制元件都能手勢縮放、移動、雙擊還原:
1.實現思路:與上面的方法基本沒什麼區別,只需要把繼承ImageView改為繼承FrameLayout,但是FrameLayout沒有setImageMatrix方法,我們進入ImageView的原始碼中可以看出setImageMatrix方法主要是將matrix賦值給了mDrawMatrix,而mDrawMatrix在onDraw方法中作用在了canvas上canvas.concat(mDrawMatrix),從而使圖片實現矩陣中的相關操作,所以我們的FrameLayout雖然沒有setImageMatrix這個方法,但是我們只需要在Matrix相關操作賦值後,呼叫invalidate()方法重繪介面,並且在它的onDraw方法中使canvas與矩陣關聯就行了,需要注意的是ViewGroup出於效率的考慮有些是預設繞過onDraw方法的,所以我們需要重寫dispatchDraw方法而不是onDraw方法。
2.實現程式碼:
1.將所有的setImageMatrix方法替換成invalidate方法;
2.重寫dispatchDraw方法:
@Override protected void dispatchDraw(Canvas canvas) { View view = getChildAt(0); canvas.save(); canvas.concat(mMatrix); view.draw(canvas); canvas.restore(); }
3.使用注意事項:
在自定義佈局中只能包含一個ViewGroup控制元件,然後所有的子view都放到這個ViewGroup中才能實現預期效果,類似ScrollerView的使用。
五、程式碼下載,我上傳的是android studio裡的一個module,請自行建立project匯入使用。