之前那篇文章记录了我学习图片上批注的过程, http://blog.csdn.net/baidu_33546245/article/details/55684699
不过上述的那个项目只是作为学习原理的代码,离真实的项目需求还是有很大差距的,
本来是想等功能稳定后补全下代码…但是前段空闲时间在学习python爬虫,再加上日常工作比较忙,就没有继续更新.
直到前几天打开github,发现有人竟然star了我的那个项目,荣获人生中第一个star,欣喜若狂,周末决定开坑继续把该模块碰到的坑全部补全.
分解例一下还需要做哪些方面的改进:
之前的项目是继承了view的一个控件,而实际使用时我们显示图片的view有很大可能要放到viewpager这种滑动列表中去,并且真实项目中去调用这个功能所需的代码的不可能太多,
否则很影响其它人的开发效率.我们想要在图片上进行绘制,必须在滑动列表中,找到当前view的对象.
在真实业务逻辑中保存绘制,不同大小图片的点,就必须先知道图片的宽高,这对业务逻辑开展是非常不利的,意味着业务代码会增加很多.
增加一些其它的功能,如绘制正方形,椭圆,箭头(因为箭头的算法是一个大神同事写的,暂时不上传),还有点击某个线条删除.
双缓冲绘制
首先,先了解一下双缓存绘制的概念,如果我们每次刷新都去重新绘制的话,图形比较少的话,可能没有什么问题.而如多一旦绘制了上百条线,每次刷新对处理器来说都是很大的压力.,所以要使用双缓冲机制,
Android第一个缓冲区就是在我们的onDraw方法里,可以尝试在onDraw里先绘制一个圆形,过1s再绘制一个正方形,你会发现圆形和正方形是一起被画出来的,并没有像我们想象的那样,首先出现圆形,再出现正方形(这个结论并没有试过,是看的hongyang大神的博客里的结果).这是因为onDraw是把信息先画到缓冲区,然后再统一画到画布上,这就是第一个缓冲.
第二个缓冲区是由我们手动创建控制的的,就是用一个bitmap去记录历史绘制信息,
mCanvasBitmap = Bitmap.createBitmap(getWidth(),getHeight(),Bitmap.Config.ARGB_4444);
mCanvas = new Canvas(mCanvasBitmap);
画线的时候:在手指按下,移动的时候,直接在绘制当前一次操作按下移动的轨迹,等手指抬起就就把刚才一笔信息画在mCanvasBitmap上,这样就有效防止了过度绘制…
1, 使用Glide加载自定义控件
为了解决1,我们就需要使用一些安卓常用的图片加载控件来加载我们的图片,我选择了Glide,
因为控件初始化的时候需要知道图片实际的宽高,以便确定后摆在屏幕中间.考虑到在viewPager中的使用以及使用Glide这种控件进行加载,所以自定义控件还是继承ImageView好,
最开始采用了监听图片布局全局变化的方法,判断长宽大于bitmap的长宽就自动赋值的方法.后来在测试时发现,这种方法有可能控件的长宽有可能不大于bitmap的长宽,就造成了初始化图片失败.
后来简单看了一下Glide的源码,发现它加载图片最终会调用imageview的setImageDrawable()的方法..于是就把初始化的代码放在seetImageDrawable后执行.
2,在ViewPager中找到当前的位置的对象
解决第二个问题:
在viewpager滑动中,必须根据当前位置找到当前页的图片对象,我的做法是给加载的图片设置当前位置的TAG,然后记录当前位置,
在viewpager中根据位置寻找当前位置的画板对象,然后操作PaintImageView,我是用Glide加载图片的,因为Glide默认使用了setTag的方法,
所以我们需要给Glide指定一个其它标记,在初始化时,让Glide使用我们设置的标记,这样我们就可以设置标记了
//初始化Glide的代码(清单文件中)
<meta-data
android:name="com.demo.drawpaintview.MyGlideMoudle"
android:value="GlideModule" />
`//初始化时改变Glide的TAG``
public class MyGlideMoudle implements GlideModule {
@Override
public void applyOptions(Context context, GlideBuilder builder) {
ViewTarget.setTagId(R.id.glide_tag_id);
}
@Override
public void registerComponents(Context context, Glide glide) {
}
}
3, 建立逻辑坐标系,简化交互流程
解决第三个问题:
为了简化信息传输,初始化逻辑,我们将拿到的点转化为(100,100)坐标系的相对位置,这样的好处就是,无须知道图片的宽高,只要两台设备有相同的该张图片,就可以根据自己测量的图片宽高来初始化点线,一定程度上减少了业务的同步交互逻辑.
也就是说现在是这个流程:
我们拿到的触摸点 —> 按当前矩阵求逆,获取在图片上的原始坐标系的相对点 ——–>
x,y分别除以宽,高,再 * 100,获取逻辑(logic)坐标系内的点——–>
实时发送给其它设备或者实时记录到数据库,内存, —–>
将逻辑坐标系的点根据当前的图片宽高,还原到绘制坐标系, ——>
使用绘制坐标系的点绘制.
该模块处理坐标系的相关代码被统一抽出到TransPoint类中…
4,绘制长方形和椭圆,增加点击线条删除的逻辑
1,画长方形: 长方形只需要知道起始点和最终点就可以了,直接调用Android的 canvas.drawRect(dx, sy, sx, dy, paint);方法
2,画椭圆: 椭圆同样很简单, 直接ctrl + p 查看要传的参数,发现可以传一个长方形确定椭圆,canvas.drawOval(new RectF(moveX, moveY, downX, downY), mPaint);
3,点击线条删除: 点击某个线条删除就比较麻烦了,我是这样做的,:
把所有的线条生成相应的path —->
然后根据path生成一个图形 ——->
根据触摸点生成一个长方形 ———>
判断两个长方形是否相交———–>
如果相交,就返回当前一笔的id,根据id,删除掉图形中的那条直线————->
重新绘制图形…
最后仍然附上代码的github地址,大家可以去看一下代码:
https://github.com/textview10/DrawPaintView