我分四部分介绍:
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).get(), new WeakReference (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= 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).get(), new WeakReference (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导入使用。