有关Canvas图像覆盖问题

xiaoxiao2021-02-28  111

概述

项目开发过程中使用了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/51264653http://blog.csdn.net/iispring/article/details/50472485http://www.cnblogs.com/DonkeyTomy/articles/3215137.htmlhttp://www.cnblogs.com/tianzhijiexian/p/4297172.htmlhttp://blog.csdn.net/u011433995/article/details/50475131http://blog.csdn.net/scnuxisan225/article/details/49702979
转载请注明原文地址: https://www.6miu.com/read-67483.html

最新回复(0)