加了大部分註釋,看註釋應該能夠明白基本的思路。歡迎大神留言拍磚,此文適合小白觀看。java
package com.example.imagedeal; import android.content.Context; import android.graphics.Matrix; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.view.GestureDetector; import android.view.GestureDetector.SimpleOnGestureListener; 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; /* * @OnGlobalLayoutListener 手勢縮放監聽 * @OnScaleGestureListener 得到控件寬高 * @OnTouchListener 觸摸事件監聽器 */ public class CustomImage extends ImageView implements OnGlobalLayoutListener,OnScaleGestureListener,OnTouchListener{ private Matrix matrix; private ScaleGestureDetector scaleGestureDetector; private int touchSlop; //判斷滑動距離的最小值,大於此值,才認爲滑動 private GestureDetector gestureDetector; public CustomImage(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); matrix = new Matrix(); scaleGestureDetector = new ScaleGestureDetector(context, this); setOnTouchListener(this); touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); //@Focous實現此功能的第六步,雙擊縮小放大 gestureDetector = new GestureDetector(context, new SimpleOnGestureListener(){ @Override public boolean onDoubleTap(MotionEvent e) { float x = e.getX(); float y = e.getY(); float currScale = getCurrScale(); //縮小 if (currScale>=doubleScale) { //matrix.postScale(defScale/currScale, defScale/currScale, x, y); post(new SlowScale(x, y, defScale)); }else{ post(new SlowScale(x, y, doubleScale)); //matrix.postScale(doubleScale/currScale, doubleScale/currScale, x, y); } checkBorderByScale(); setImageMatrix(matrix); return true; } }); } /* * 實現梯度縮放 */ class SlowScale implements Runnable{ private float x,y,targetScale; private float tempScale;//臨時縮放比例 private static final float BIGGER = 1.05f; private static final float SMALLER = 0.95f; public SlowScale(float x, float y, float targetScale) { this.x = x; this.y = y; this.targetScale = targetScale; float scale = getCurrScale(); if (scale>=targetScale) { tempScale = SMALLER; }else{ tempScale = BIGGER; } } @Override public void run() { matrix.postScale(tempScale, tempScale, x, y); checkBorderByScale(); setImageMatrix(matrix); if ((getCurrScale()>targetScale&&tempScale<1.0f)|| getCurrScale()<targetScale&&tempScale>1.0f) { postDelayed(this, 20); }else{ matrix.postScale(targetScale/getCurrScale(), targetScale/getCurrScale(), x, y); checkBorderByScale(); setImageMatrix(matrix); } } } public CustomImage(Context context, AttributeSet attrs) { this(context, attrs,0); } public CustomImage(Context context) { this(context,null); } /*@Focus 實現此功能的第一步 *@來源 實現View類就能夠重寫此方法 *@onAttachedToWindow() 在第一次調用onDraw方法以前調用,在此用來註冊觀察者類(getViewTreeObserver()) *@getViewTreeObserver() 時間的觀察者,用來註冊addOnGlobalLayoutListener監聽器。實現對手勢的監聽 */ @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getViewTreeObserver().addOnGlobalLayoutListener(this);//詳見http://blog.csdn.net/x1617044578/article/details/39668667 } /*@Focus 實現此功能的第一步 *@來源 實現View類就能夠重寫此方法 *@onDetachedFromWindow() 在銷燬View以後調用,作收尾工.在此用來取消註冊觀察者類(getViewTreeObserver()) */ @SuppressWarnings("deprecation") @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getViewTreeObserver().removeGlobalOnLayoutListener(this); } /* * @isInited 至關於鎖,讓程序對控件之類的寬高只進行一次計算 * @defScale 默認的縮放比率 * @doubleScale 雙擊縮放比率 * @maxScale 最大縮放比率 * @Focus 實現此功能的第二步,測量控件和圖片的寬高,而且計算縮放率 */ private boolean isInited; private int width,height,dw,dh;//with,height:控件的寬高 dw,dh圖片的寬高 private float defScale,doubleScale,maxScale; @Override public void onGlobalLayout() { if (!isInited) { width = getWidth(); height = getHeight(); Drawable drawable = getDrawable(); if (drawable==null) { return; } dw = drawable.getIntrinsicWidth(); dh = drawable.getIntrinsicHeight(); float scaleX = width*1.0f/dw; //X軸上的縮放量,即寬的縮放量 float scaleY = height*1.0f/dh; //Y軸上的縮放量,即高的縮放量 float scale = 1.0f; //初始的縮放量,1.0,即不縮放。 if ((dw>width&&dh>height)||(dw<width&&dh<height)) { //圖片比屏幕大或者圖片比屏幕小都進入 scale = Math.min(scaleX, scaleY); //縮小時,取小是由於無論哪一種狀況,說明最小的那個值對應的圖片的屬性最大,讓它顯示,屬性小的那一方絕對會正確顯示。放大時本身思考! }else if(dw>width||dh>height){ //圖片的一個屬性比屏幕大都會進入此 scale = (scaleX>scaleY)?scaleY:scaleX; //縮放時取最小值保證dw與dh較大的那一方能夠鋪滿屏幕,較小的留白顯示,上面的也是一個效果。 } defScale = scale; doubleScale = defScale*2; maxScale = defScale*4; float dx = (width-dw)/2; //平移時X軸的中心點 float dy = (height-dh)/2; //平移時Y軸的中心點 matrix.postTranslate(dx, dy); //先平移 matrix.postScale(defScale, defScale, width/2, height/2); //再縮放 setImageMatrix(matrix); isInited = true; } } /* * 得到當前的縮放比率 */ private float getCurrScale(){ float[] values = new float[9]; matrix.getValues(values ); return values[Matrix.MSCALE_X]; } /* *@onScale() 縮放時時時調用此方法 *@URL http://blog.csdn.net/lmj623565791/article/details/39474553 對Matrix和縮放的應用介紹的還好 */ @Override public boolean onScale(ScaleGestureDetector detector) { float scaleFactor = detector.getScaleFactor(); //縮放前的縮放比率 float currScale = getCurrScale(); //當前的縮放比率 float scale = currScale*scaleFactor; //當前的縮放值比最大縮放值小 想放大 當前的縮放值大於初始縮放值 想縮小 if ((currScale<maxScale&&scaleFactor>1.0f)|| (currScale>defScale&&scaleFactor<1.0f)) { if (scale>maxScale) { scaleFactor = maxScale/currScale; //放大時,控制縮放比率,不超過最大值 } if (scale<defScale) { scaleFactor = defScale/currScale;//縮小時,控制縮放比率,不超過最小值 } matrix.postScale(scaleFactor, scaleFactor, detector.getFocusX(), detector.getFocusY()); //在detector.getFocusX()(X軸上的觸摸焦點)點與在detector.getFocus()(軸上的觸摸焦點)上進行縮放 checkBorderByScale(); //檢查縮放時不能留白 setImageMatrix(matrix); } return true; } private RectF getMatrixRectF(){ RectF rectF = new RectF(0, 0, dw, dh); matrix.mapRect(rectF); return rectF; } /* * @Focus 實現此功能的第三步,保證縮放時不越界,實現辦法是平移圖片至控件中心點 * @width 控件的寬 * @height 控件的高 */ private void checkBorderByScale() { RectF rectF = getMatrixRectF(); //將當前圖像抽象爲矩形,得到長寬 float dx=0 ,dy = 0; //X,Y軸上的平移量 //防止出現白邊 縮小 if (rectF.width()>=width) { //判斷是左邊和右邊哪邊越界 if (rectF.left>0) { dx = -rectF.left; } if (rectF.right<width) { dx = width - rectF.right; } } //判斷是上下哪邊越界 if (rectF.height()>=height) { if (rectF.top>0) { dy = -rectF.top; } if (rectF.bottom<height) { dy = height-rectF.bottom; } } //圖片居中 if (rectF.width()<width) { dx = width/2f -rectF.right+rectF.width()/2f; } if (rectF.height()<height) { dy = height/2f-rectF.bottom+rectF.height()/2f; } matrix.postTranslate(dx, dy); } @Override public boolean onScaleBegin(ScaleGestureDetector detector) { // 返回true return true; } @Override public void onScaleEnd(ScaleGestureDetector detector) { } /* * @Focus 實現此功能的第四步,拖動 */ private int prePointerCount; //觸摸時前一次的觸摸點的數 private float preX,preY; //觸摸時前一次的X點和Y點的座標 private boolean isDrag; //是否拖拽 private boolean checkLeftAndRight,checkTopAndBottom; @Override public boolean onTouch(View v, MotionEvent event) { if (gestureDetector.onTouchEvent(event)) { return true; //閱讀http://blog.csdn.net/tianfeng701/article/details/7556366 } scaleGestureDetector.onTouchEvent(event); int pointerCount = event.getPointerCount(); //得到觸摸點的數 //記錄中心點的座標 float x =0,y = 0; for(int i = 0 ; i<pointerCount;i++){ x+= event.getX(i); y+= event.getY(i); } //計算觸摸時的中心點 x/=pointerCount; y/=pointerCount; if (prePointerCount!=pointerCount) { preX = x; preY = y; } prePointerCount = pointerCount; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: isDrag = false; break; case MotionEvent.ACTION_MOVE: RectF rectF = getMatrixRectF(); float dx = x - preX; float dy = y - preY; if (!isDrag) { checkLeftAndRight = checkTopAndBottom = true; isDrag = checkIsDrag(dx,dy); //判斷是否要開始拖拽 } if (isDrag) { if (rectF.width()<width) { //判斷X方向有沒有超過屏幕 checkLeftAndRight = false; dx = 0; } if (rectF.height()<height) {//判斷Y方向有沒有超過屏幕 checkTopAndBottom = false; dy = 0 ; } matrix.postTranslate(dx, dy); checkBorderByTransLate(); //檢查邊框 setImageMatrix(matrix); } preX = x; preY = y; break; case MotionEvent.ACTION_UP: prePointerCount = 0; //鬆手時觸摸點歸零 break; } return true; } private void checkBorderByTransLate() { RectF rectF = getMatrixRectF(); float dx=0,dy=0; if (checkLeftAndRight&&rectF.left>0) { dx = -rectF.left; } if (checkLeftAndRight&&rectF.right<width) { dx = width - rectF.right; } if (checkTopAndBottom&&rectF.top>0) { dy = -rectF.top; } if (checkTopAndBottom&&rectF.bottom<height) { dy = height-rectF.bottom; } matrix.postTranslate(dx, dy); } private boolean checkIsDrag(float dx, float dy) { return Math.sqrt(dx*dx+dy*dy)>touchSlop; } }