有关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方法

参考链接

  1. http://blog.csdn.net/harvic880925/article/details/51264653
  2. http://blog.csdn.net/iispring/article/details/50472485
  3. http://www.cnblogs.com/DonkeyTomy/articles/3215137.html
  4. http://www.cnblogs.com/tianzhijiexian/p/4297172.html
  5. http://blog.csdn.net/u011433995/article/details/50475131
  6. http://blog.csdn.net/scnuxisan225/article/details/49702979

0 0