Android实现对图片放大、平移和旋转的功能
先来看看要实现的效果图
在讲解中,需要大家提前了解一些关于图片绘制的原理的相关知识。
关于实现的流程
1、自定义view
2、获得操作图片的bitmap
3、复写view
的ontouchevent()
方法中的action_down
,action_pointer_down
,action_move
,action_pointer_up
以及action_up
事件。
4、定义相应图片变化的matrix
矩阵,通过手势操作的变化来设置相应的matrix
。
5、完成最终的matrix
设置时,通过invalidate()
方法重新绘制页面。
那么接下来我们根据以上流程一步一步实现代码。
代码演示
/** * 作者:zhouyou * 日期:2016/8/23. */ public class touchimageview extends view { // 绘制图片的边框 private paint paintedge; // 绘制图片的矩阵 private matrix matrix = new matrix(); // 手指按下时图片的矩阵 private matrix downmatrix = new matrix(); // 手指移动时图片的矩阵 private matrix movematrix = new matrix(); // 资源图片的位图 private bitmap srcimage; // 多点触屏时的中心点 private pointf midpoint = new pointf(); // 触控模式 private int mode; private static final int none = 0; // 无模式 private static final int trans = 1; // 拖拽模式 private static final int zoom = 2; // 缩放模式 // 是否超过边界 private boolean withinborder; public touchimageview(context context) { this(context, null); } public touchimageview(context context, attributeset attrs) { this(context, attrs, 0); } public touchimageview(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); init(); } private void init() { paintedge = new paint(); paintedge.setcolor(color.black); paintedge.setalpha(170); paintedge.setantialias(true); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); srcimage = bitmapfactory.decoderesource(getresources(), r.mipmap.ic_avatar_1); } @override protected void ondraw(canvas canvas) { super.ondraw(canvas); float[] points = getbitmappoints(srcimage, matrix); float x1 = points[0]; float y1 = points[1]; float x2 = points[2]; float y2 = points[3]; float x3 = points[4]; float y3 = points[5]; float x4 = points[6]; float y4 = points[7]; // 画边框 canvas.drawline(x1, y1, x2, y2, paintedge); canvas.drawline(x2, y2, x4, y4, paintedge); canvas.drawline(x4, y4, x3, y3, paintedge); canvas.drawline(x3, y3, x1, y1, paintedge); // 画图片 canvas.drawbitmap(srcimage, matrix, null); } // 手指按下屏幕的x坐标 private float downx; // 手指按下屏幕的y坐标 private float downy; // 手指之间的初始距离 private float olddistance; // 手指之间的初始角度 private float oldrotation; @override public boolean ontouchevent(motionevent event) { int action = motioneventcompat.getactionmasked(event); switch (action) { case motionevent.action_down: mode = trans; downx = event.getx(); downy = event.gety(); downmatrix.set(matrix); break; case motionevent.action_pointer_down: // 多点触控 mode = zoom; olddistance = getspacedistance(event); oldrotation = getspacerotation(event); downmatrix.set(matrix); midpoint = getmidpoint(event); break; case motionevent.action_move: // 缩放 if (mode == zoom) { movematrix.set(downmatrix); float deltarotation = getspacerotation(event) - oldrotation; float scale = getspacedistance(event) / olddistance; movematrix.postscale(scale, scale, midpoint.x, midpoint.y); movematrix.postrotate(deltarotation, midpoint.x, midpoint.y); withinborder = getmatrixbordercheck(srcimage, event.getx(), event.gety()); if (withinborder) { matrix.set(movematrix); invalidate(); } } // 平移 else if (mode == trans) { movematrix.set(downmatrix); movematrix.posttranslate(event.getx() - downx, event.gety() - downy); withinborder = getmatrixbordercheck(srcimage, event.getx(), event.gety()); if (withinborder) { matrix.set(movematrix); invalidate(); } } break; case motionevent.action_pointer_up: case motionevent.action_up: mode = none; break; default: break; } return true; } /** * 获取手指的旋转角度 * * @param event * @return */ private float getspacerotation(motionevent event) { double deltax = event.getx(0) - event.getx(1); double deltay = event.gety(0) - event.gety(1); double radians = math.atan2(deltay, deltax); return (float) math.todegrees(radians); } /** * 获取手指间的距离 * * @param event * @return */ private float getspacedistance(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); } /** * 获取手势中心点 * * @param event */ private pointf getmidpoint(motionevent event) { pointf point = new pointf(); float x = event.getx(0) + event.getx(1); float y = event.gety(0) + event.gety(1); point.set(x / 2, y / 2); return point; } /** * 将matrix的点映射成坐标点 * * @return */ protected float[] getbitmappoints(bitmap bitmap, matrix matrix) { float[] dst = new float[8]; float[] src = new float[]{ 0, 0, bitmap.getwidth(), 0, 0, bitmap.getheight(), bitmap.getwidth(), bitmap.getheight() }; matrix.mappoints(dst, src); return dst; } /** * 检查边界 * * @param x * @param y * @return true - 在边界内 | false - 超出边界 */ private boolean getmatrixbordercheck(bitmap bitmap, float x, float y) { if (bitmap == null) return false; float[] points = getbitmappoints(bitmap, movematrix); float x1 = points[0]; float y1 = points[1]; float x2 = points[2]; float y2 = points[3]; float x3 = points[4]; float y3 = points[5]; float x4 = points[6]; float y4 = points[7]; float edge = (float) math.sqrt(math.pow(x1 - x2, 2) + math.pow(y1 - y2, 2)); if ((2 + math.sqrt(2)) * edge >= math.sqrt(math.pow(x - x1, 2) + math.pow(y - y1, 2)) + math.sqrt(math.pow(x - x2, 2) + math.pow(y - y2, 2)) + math.sqrt(math.pow(x - x3, 2) + math.pow(y - y3, 2)) + math.sqrt(math.pow(x - x4, 2) + math.pow(y - y4, 2))) { return true; } return false; } }
我已经在代码中针对可能遇到的问题做了详细的注释。
1. matrix
// 绘制图片的矩阵 private matrix matrix = new matrix(); // 手指按下时图片的矩阵 private matrix downmatrix = new matrix(); // 手指移动时图片的矩阵 private matrix movematrix = new matrix();
首先我定义了三个matrix
变量,目的在于通过不同手势的操控图片的matrix
最终由绘制图片的matrix
所接收,因此需要在不同的操作中使用不同的matrix
进行图形变换的数据传递,从而在渲染页面的时候将最终的matrix
再传递回绘图的matrix
。
2. pointf
// 多点触屏时的中心点 private pointf midpoint = new pointf();
因为如果是针对图片的旋转和放大操作,需要通过两个手指进行控制,因此我们需要知道在多个手指触摸屏幕时的中心点坐标。
3. 触控模式
// 触控模式 private int mode; private static final int none = 0; // 无模式 private static final int trans = 1; // 拖拽模式 private static final int zoom = 2; // 缩放模式
在ontouchevent()
事件中,会根据不同的事件变换触控的模式,从而进行不同图片变换的操作。
4. ontouchevent()
首先,我们是自定义的view
,因此如果要对该事件进行消费的话,需要将返回值设置为true
。
(1)action_down - 该事件是单点触屏的事件,也就是说如果一个手指按下屏幕的时候就会回调这个事件。那么我们在该事件中就将触控模式设置为拖拽模式(trans),记录下按下屏幕的xy坐标,并在这个事件中将绘图的matrix复制给按下屏幕的matrix。
case motionevent.action_down: mode = trans; downx = event.getx(); downy = event.gety(); downmatrix.set(matrix); break;
(2)action_pointer_down - 这个事件发生在超过一个手指触摸屏幕的时候。我们在这个事件中即可针对多点触屏的操作进行初始化设置。在该事件中,我们将触控模式重新设置为(zoom),初始化两指之间触摸屏幕的距离以及两指之间的旋转角度,初始化两指之间的中心点坐标。最后把绘图的matrix复制给按下屏幕的matrix。
case motionevent.action_pointer_down: // 多点触控 mode = zoom; olddistance = getspacedistance(event); oldrotation = getspacerotation(event); midpoint = getmidpoint(event); downmatrix.set(matrix); break;
(3)action_move - 到了移动的事件中,根据之前的触控模式进行判断。首先,将按下事件的matrix复制给移动事件的matrix。如果是(zoom)模式,我们将会根据事件获得手指旋转角度的差值,以及手指之间距离的差值。根据这两个差值,以及在action_down事件中获得的中点坐标,我们即可设置move事件的缩放和旋转。(trans)模式也是如此。最后通过获取图片变换的边界值来判断是否进行绘图渲染。
case motionevent.action_move: // 缩放 if (mode == zoom) { movematrix.set(downmatrix); float deltarotation = getspacerotation(event) - oldrotation; float scale = getspacedistance(event) / olddistance; movematrix.postscale(scale, scale, midpoint.x, midpoint.y); movematrix.postrotate(deltarotation, midpoint.x, midpoint.y); withinborder = getmatrixbordercheck(srcimage, event.getx(), event.gety()); if (withinborder) { matrix.set(movematrix); invalidate(); } } // 平移 else if (mode == trans) { movematrix.set(downmatrix); movematrix.posttranslate(event.getx() - downx, event.gety() - downy); withinborder = getmatrixbordercheck(srcimage, event.getx(), event.gety()); if (withinborder) { matrix.set(movematrix); invalidate(); } } break;
(4)action_pointer_up和action_up - 在这两个事件中,重新将触屏的模式设置会none。
5. 边界判断
以下即为边界判断的逻辑是针对正方形的图片来说的。首先通过原图片相对自己四个坐标映射成为matrix对应屏幕的点坐标。通过得到4个点的坐标,我们即可根据手指触摸图片时的坐标与图片的4个点坐标进行关联。
边界判断的逻辑是手指触摸图片的点到4个顶点的距离之和如果小于(2+根号2倍)的斜边长度,即视为不超过边界。
/** * 将matrix的点映射成坐标点 * * @return */ protected float[] getbitmappoints(bitmap bitmap, matrix matrix) { float[] dst = new float[8]; float[] src = new float[]{ 0, 0, bitmap.getwidth(), 0, 0, bitmap.getheight(), bitmap.getwidth(), bitmap.getheight() }; matrix.mappoints(dst, src); return dst; } /** * 检查边界 * * @param x * @param y * @return true - 在边界内 | false - 超出边界 */ private boolean getmatrixbordercheck(bitmap bitmap, float x, float y) { if (bitmap == null) return false; float[] points = getbitmappoints(bitmap, movematrix); float x1 = points[0]; float y1 = points[1]; float x2 = points[2]; float y2 = points[3]; float x3 = points[4]; float y3 = points[5]; float x4 = points[6]; float y4 = points[7]; float edge = (float) math.sqrt(math.pow(x1 - x2, 2) + math.pow(y1 - y2, 2)); if ((2 + math.sqrt(2)) * edge >= math.sqrt(math.pow(x - x1, 2) + math.pow(y - y1, 2)) + math.sqrt(math.pow(x - x2, 2) + math.pow(y - y2, 2)) + math.sqrt(math.pow(x - x3, 2) + math.pow(y - y3, 2)) + math.sqrt(math.pow(x - x4, 2) + math.pow(y - y4, 2))) { return true; } return false; }
总结
好了,本文的内容到这就结束了,完成了以上的步骤,即可完成针对图片在屏幕上的放大、平移和旋转的操作。是不是还是很简单的。有兴趣的可以自己动手操作起来,希望这篇文章对大家的学习和工作能有所帮助,如果有疑问可以留言交流,谢谢大家对的支持。