有关Canvas图像覆盖问题
来源:互联网 发布:常用协议端口号 编辑:程序博客网 时间:2024/06/05 15:39
概述
项目开发过程中使用了MPAndroidChart开源库,在绘制自定义Marker时出现了图像覆盖的问题。类似效果如下图所示:
图中有三个圆形顶点,两条线段和三个矩形标签。而预期效果是顶点始终位于标签的下方。本文针对该问题,阐述具体的解决方法。
问题复现
单独创建一个新项目用于模拟Marker的绘制过程。MPAndroidChart的绘制过程是先画直线,然后再画Marker。我们自定义的Marker包含两部分,即顶点和标签。
- 自定义View,在其
onDraw()
方法中进行折线、Marker的绘制。
//画笔 private Paint mPaint = new Paint(); //起始顶点 private float[] mStart = {200,400}; //中间顶点 private float[] mMiddle = {260,500}; //末尾顶点 private float[] mStop = {300,420}; @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); //填充背景色 canvas.drawColor(Color.YELLOW); //绘制折线 drawLines(canvas); //绘制Marker drawMarker(canvas,mStart,Color.RED); drawMarker(canvas,mMiddle,Color.GREEN); drawMarker(canvas,mStop,Color.BLUE); } private void drawLines(Canvas canvas){ mPaint.setColor(Color.GRAY); mPaint.setStrokeWidth(5); //绘制线段 canvas.drawLine(mStart[0],mStart[1],mMiddle[0],mMiddle[1],mPaint); canvas.drawLine(mMiddle[0],mMiddle[1],mStop[0],mStop[1],mPaint); } private void drawMarker(Canvas canvas,float[] point,int color) { mPaint.setColor(color); //绘制顶点 canvas.drawCircle(point[0], point[1], 10, mPaint); float x = point[0] - 60; float y = point[1] - 120; RectF rectF = new RectF(x, y, x + 120, y + 60); //绘制标签 canvas.drawRect(rectF, mPaint); }
- 在布局文件中引入该自定义View,最终显示效果如下图:
可以看到蓝色顶点位于绿色标签的上方,与预期效果不符。
分析及解决
通过调用Canvas
对象的drawXXX()
方法,我们依次绘制了折线和Marker。参考有关博客: 每次Canvas画图时(即调用Draw系列函数),都会产生一个透明图层,然后在这个图层上画图,画完之后覆盖在屏幕上显示。通过这种解释不难理解最后绘制的蓝色顶点位于绿色标签的上方。(有待考证)
使顶点位于标签的下方,涉及到图像的混合模式。Paint
提供有setXfermode(Xfermode xfermode)
方法来处理图像的混合。
其中PorterDuffXfermode
是惟一一个没有过时且沿用至今的子类,它提供了如下混合效果:
其中,Dst为目标图像,表示画布上已有的图像,而Src为源图像,表示当前正要绘制的图像。在Android的PorterDuff.Mode类中列举了他们制定的规则:
android.graphics.PorterDuff.Mode.SRC:只绘制源图像
android.graphics.PorterDuff.Mode.DST:只绘制目标图像
android.graphics.PorterDuff.Mode.DST_OVER:在源图像的顶部绘制目标图像
android.graphics.PorterDuff.Mode.DST_IN:只在源图像和目标图像相交的地方绘制目标图像
android.graphics.PorterDuff.Mode.DST_OUT:只在源图像和目标图像不相交的地方绘制目标图像
……
参见详情
- 通过使用
Paint
对象的setXfermode()
方法,设置圆形顶点始终位于矩形标签的下方。
private void drawMarker(Canvas canvas,float[] point,int color) { mPaint.setColor(color); //设置混合模式 mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OVER)); //绘制顶点 canvas.drawCircle(point[0], point[1], 10, mPaint); //清除混合模式 mPaint.setXfermode(null); float x = point[0] - 60; float y = point[1] - 120; RectF rectF = new RectF(x, y, x + 120, y + 60); //绘制标签 canvas.drawRect(rectF, mPaint); }
运行效果如下图所示:
可以看到顶点全都消失了,这究竟是什么原因?以下对绘制的每一步进行问题排查。
刚开始我们设置了画笔的背景为黄色,然后在其上绘制了两条线段,这个没有问题;
接着开始绘制第一个Marker。我们使用
PorterDuff.Mode.DST_OVER
模式来绘制顶点,然后将顶点(即源图像)与原屏幕上的图像(即目标图像,见上图)进行混合,由于DST_OVER
模式是在源图像的顶部绘制目标图像,因而顶点被黄色背景所替代。而在绘制标签的时候,画笔的混合模式已被清除。
该问题主要是由于两图像在进行混合时存在无用的干扰元素造成的。为此可创建一个新的图层(Layer)专门用于Marker的绘制。Android的Canvas可以使用saveLayerXXX
来创建一些中间层,使用restore/restoreToCount
把本层绘制的图像“绘制”到上层或是Canvas上。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawColor(Color.YELLOW); //绘制折线 drawLines(canvas); //新建图层 int saveCount=canvas.saveLayer(getLeft(),getTop(),getRight() ,getBottom(),null,Canvas.ALL_SAVE_FLAG); //绘制Marker drawMarker(canvas,mStart,Color.RED); drawMarker(canvas,mMiddle,Color.GREEN); drawMarker(canvas,mStop,Color.BLUE); //还原图层 canvas.restoreToCount(saveCount); }
运行效果如下图所示:
总结
以上主要涉及到两个问题:
1. 图像的混合模式
Paint对象提供的setXfermode()
方法
2. 图层概念
Canvas提供的saveLayerXXX
方法
参考链接
- http://blog.csdn.net/harvic880925/article/details/51264653
- http://blog.csdn.net/iispring/article/details/50472485
- http://www.cnblogs.com/DonkeyTomy/articles/3215137.html
- http://www.cnblogs.com/tianzhijiexian/p/4297172.html
- http://blog.csdn.net/u011433995/article/details/50475131
- http://blog.csdn.net/scnuxisan225/article/details/49702979
- 有关Canvas图像覆盖问题
- Android签名与程序覆盖有关问题
- 有关IO流中覆盖问题
- canvas(图像)
- Android中图像处理有关问题释疑
- 关于HTML5中Canvas的宽、高设置有关问题
- 贪心法求解三种有关区间覆盖问题
- 贪心法求解三种有关区间覆盖问题
- canvas覆盖透明
- HTML5中canvas标签加载图像的问题
- 有关内存覆盖
- 有关PHP动态生成图像中文乱码问题
- 有关OpenCV中图像深度的问题,期待有人回答
- Canvas显示图像
- canvas 图像合成
- HTML5 Canvas图像缩放
- canvas操作图像
- canvas三 图像处理
- Unity用Lua开发的 使用度 问题
- oracle删除添加表空间
- 函数——Python学习笔记03
- hdoj 2586
- POJ3177【边双连通分量缩点】
- 有关Canvas图像覆盖问题
- 为什么用接口存常量是一种不良的习惯
- C语言小游戏————反弹球(简单的图形化界面)
- JSON解析类库之Gson(4) --- TypeAdapter接管序列化与反序列化
- android developer tiny share-20170506
- 重置CSS样式表
- Mac OS X配置Java环境变量
- [14]内置对象
- drizzleDumper的原理分析和使用说明