Android在图片画线(放大,旋转,平移后可继续正确画线,限制画线区域)

来源:互联网 发布:淘宝店铺永久封店2016 编辑:程序博客网 时间:2024/06/05 18:15
最近因为公司业务需求,需要给学校的老师们做一个在手机和电脑上同步进行图片上画线批注的效果.刚开始在github上看了几天,但跟我们要求吻合的开源项目几乎没有.最后只能自己来实现一个相应的功能,经过一定时间研究相关信息,有了这个自定义view,记录一下自己的学习过程

在经过跟pm的讨论后,确定了大致4个要求,也是图片的几个主要功能吧,
1,整个图片的批注显示和放大处理逻辑应该放在自定义view里来做,方便之后的迭代升级.
2,要实现在图片区域内画线,经过旋转或者放大后,批注信息应该同步变化,画线区域也要跟着变化,
3,手势放大,放大后拖动可以移动放大后的图片.
4,画线信息应该可以单独保存(暂时存在内存中,以便使用Android的滑动控件viewpager加载时可以做到再次显示,以后有可能要存到数据库,)
旋转后的画线效果

对图片画线区域的限制,放大或旋转后同样有效
对图片画线边界的限制

我们这里主要分三点进行讨论:

一,如何画出流畅圆润的线条:

好在Android已经提供了丰富的画线的api给我们,大致的流程就是每一条线新建Path和Paint,path顾名思义就是线条,Paint是画笔,画笔记录的对我们比较重要的信息有绘制的颜色,粗细,是否是实心等,方法分别是setColor(currntColor);setStrokeWidth(currentPaintSize); setStyle(Paint.Style.STROKE).
开始绘制:

1,按下:新建path和paint对象,在手指按下的那一瞬间,获取按下的那个点,调用path.moveto(x,y),把path移过去
2,移动:手指不断移动移动,可以通过移动时motionevent的move返回点的信息,在两个坐标间绘制直线和贝塞尔曲线,Android对应提供了
path.lineTo(x,y)和path.quadTo(preX,preY,controlX,controlY)给我们,我们调用这两个方法就可以不断绘制线条了,关于贝塞尔曲线大家应该都知道,网上也到处都是资料,quadto()的四个参数分别是前一个点的x坐标,前一个点的y坐标,控制点的x坐标,控制点的y坐标…如何求控制点呢?这里有固定公式的(preX+currentX) / 2..实际绘制中因为折线不太美观,大部分都是绘制贝斯尔曲线.

3,抬起,抬起意味这这一笔已经完成了..如果有完美强迫症,可以调用path.quadto()来完成最后一次的连接..

好了,了解过绘制直线的流程后,我们就可以针对业务流程和自身需要来建立对象来记录这一笔绘制的信息了,因为我个人要跟pc客户端通过udp做实时的绘制交互,所以我实时记录绘制过程中的所有点.不贴全部代码了,只贴对象的基本信息

public class LineInfo implements Serializable {        private String lineId;  //线条的id        private Paint paint = new Paint();    //画笔的paint        private ArrayList<PointInfo> currentPointLists = new ArrayList<>(); //一笔中所有的点}public class PointInfo implements Serializable {        private PointF prePointF;   //上一个点的坐标        private PointF pointF;      //当前点的坐标        private int index;          //当前是第几个点}

接着我们就要把这些线条绘制画布Canvas上去了,我采用的思路是在画布上先画经过适应处理过的Bitmap,然后在之上再绘制一个用于缓存已绘制线条信息的透明Bitmap.这样可以有效的确定绘制的边界,比着每次刷新都重新绘制所有线条而言,也极大的提高了绘制效率.这时候我们还需要处理实时绘制的这一笔的线条的实时显示,这里我使用了一个加过锁的list集合,一边动态添加点,一边实时绘制当前这一次线条的点.最后不要忘了调用view里的invalidate()方法来对画布Canvas.

2 了解了如何画线条和保存,我们就可以进行下一步了:如何对图片和点的信息进行旋转和放大?

想要完成对图片和点的旋转和放大就一定要跟矩阵(Matrix)打交道,这就涉及到一些坐标系和线性代数的基本常识了.Android同样提供了丰富的api供我们使用..首先什么是矩阵?矩阵是一个3*3的数组        { 0, 0 ,0          0, 0 ,0          0, 0 ,0}    这就是Andorid中的三维矩阵.

它负责记录了图片旋转,放大,偏移的一系列变化,我们只要记录当前图片的要变化的matrix,就可以知道我们的图片和点要怎么映射到变化后的位置了;同理我们在旋转之后也可以获取当前坐标系的点的位置,通过反向计算,获得点在最初坐标系里的位置,这里为了简化逻辑,我们直接记录和交互的点的位置.

简单介绍一下我们相关的Matrix的api吧. matrix的rotate ,scale,translate.分别有pre,set,post三类方法, pre肯定是变化之前的矩阵,,set是重新设置矩阵,post是在之前变化的基础上变化矩阵.
matrix.invert(); 翻转矩阵,意思获得一个相反的矩阵,经过矩阵变化的图片或者点再经过相反的矩阵变化就还原了…

所以,在图片旋转,偏移,放大过程再次绘制,我们获得图片点要经过相反的矩阵处理才能获得原始坐标系的点.

更改matrix后,映射变化后坐标系的点到初始坐标系代码:

      float[] tempF = {moveX, moveY};              Matrix tempMatrix = new Matrix();              mMatrix.invert(tempMatrix);              tempMatrix.mapPoints(tempF, new float[]{moveX, moveY});
这里temp[0]就是初始坐标系里的x, temp[1]就是初始坐标系里的y

3,如何处理放大和放大后拖动的相关手势

好吧,你们应该猜到了,我们肯定还要用Android提供的处理手势的方法.是的,最开始我也打算自己根据触摸事件处理手势意图,但总有那么一点小问题,后来看了photoview的源码,发现它大量使用了Android的手势处理方法,根据photoview的实现逻辑,最后我采用了Android的缩放手势检查方法ScaleGestureDetector来检查缩放手势, GestureDetector来检查拖动手势.需要注意,我们要根据自己当前的状态把触摸手势发给不同的方法执行,如处于画线状态就发给画线,放大状态下在屏幕上大于一个触摸点时,就把触摸事件发给ScaleGestureDetector.如果不大于一个触摸点,并且放大倍数大于1时,发给GestureDetector;(没错,我的小学数学老师啊,你现在一定为我感到骄傲吧,多年之后,我仍然记得并能熟练运用勾股定理...).不过这种方法运算效率有些差,最后还是参考了photoview的源码来获取当前放大倍数方法,就是从当前矩阵的相应位置来获取放大倍数:
private float getScale() {        return (float) Math.sqrt((float) Math.pow(getValue(mMatrix, Matrix.MSCALE_X), 2) + (float) Math.pow(getValue(mMatrix, Matrix.MSKEW_Y), 2));    }

最后提供一下控件的调用方法的说明吧.也正好罗列一下实现的功能

    setScale(float scale) //设置当前图片在自适应控件后的放大倍数,尚不能使用    setCurrentColor(int color) //设置当前画笔颜色,下一笔生效    clear() //清空画布    undo() //撤销上一笔    setImagePath(File file); //给自定义控件设置要显示的图片    setIsScale(boolean isScaleState) //设置当前是放大状态还是批注状态    rotate() //在当前状态的基础上向右旋转90度,控件会记住旋转状态并叠加(第二次点击就会在旋转90的基础上再旋转90)    setRotate(int rotate) //设置旋转到某个角度,暂时会清空之前的放大状态,待优化.    setDrawInfo(ArrayList lineInfos) //将之前的图片绘制信息重新设置给图片..    public ArrayList getDrawInfo() //获得当前图片绘制的详情,可用于将批注的线条信息暂时保存在内存中.

大概思路就是这样,以后如果有时间,可能会补上绘制空心矩阵和圆的功能,最后感谢
http://blog.csdn.net/u012964944/article/details/52661940 ,
这篇文章给了我许多思路,感谢作者.同时使用了作者处理图片的ImageUtil这个库.

此外,还有很多细节的处理:
比如:根据view的长宽来计算图片的自适应的倍数.
图片超过最大和最小放大倍数的复位处理.

最后附上github的地址,如果感觉有帮助到您,请右上角点一个star..

https://github.com/textview10/DrawPaintView

2 0
原创粉丝点击