加了大部分注释,看注释应该可以明白基本的思路。欢迎大神留言拍砖,此文适合小白观看。
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; } }