Android中Canvas绘图之MaskFilter图文详解(附源码下载)

如果对Canvas绘图不熟悉,强烈建议您阅读博文《Android中Canvas绘图基础详解(附源码下载)》,该文对Android中的Canvas绘图基础进行了详细的描述。本文着重讲解如何使用MaskFilter创建模糊阴影以及浮雕效果。

我们知道Canvas中的各种drawXXX方法决定了绘制的几何图形的形状,而画笔Paint则决定了以什么效果绘制这些图形。Paint中有一个setMaskFilter方法,该方法接收一个MaskFilter类型的参数,MaskFilter有两个子类,分别是BlurMaskFilter和EmbossMaskFilter,可以分别用来绘制模糊阴影以及浮雕效果。如果Paint的setMaskFilter方法中传入的是null,那么就表示不使用任何MaskFilter。

为了演示不同MaskFilter对Canvas绘图的影响,我做了一个App,界面如下所示:

这里写图片描述

上面用Canvas的各种drawXXX方法绘制了各种图形,下面有三个RadioButton,默认选择的是当前绘图没有使用MaskFilter,第二项表示使用BlurMaskFilter,第三项表示使用EmbossMaskFilter。选择相应的RadioButton后,下面会出现对应的调节参数的UI界面,当然,选择第一项“无MaskFilter”后,是没有任何调节参数的UI界面的。


绘图代码

无论选择上面的哪一项,都是用同样的代码来绘制图形的,唯一不同的地方就是传递给画笔Paint的setMaskFilter的参数MaskFilter不同。绘图代码如下所示:

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int offsetX = (int)(10 * density);
        int canvasWidth = canvas.getWidth() - offsetX;
        int canvasHeight = canvas.getHeight();
        int count = 7;
        int deltaY = canvasHeight / (count + 1);
        int smallDeltaY = deltaY / (count + 1);
        canvas.translate(offsetX, 0);

        /*---------------------------绘制文本--------------------------*/
        canvas.translate(0, smallDeltaY);
        canvas.drawText("绘制文本", 0, fontSize, paint);

        /*---------------------------绘制点--------------------------*/
        canvas.translate(0, deltaY + smallDeltaY);
        int pointDeltaX = canvasWidth / 3;
        //设置画笔线宽
        paint.setStrokeWidth(20 * density);
        //绘制BUTT类型的点
        paint.setStrokeCap(Paint.Cap.BUTT);
        canvas.drawPoint(0, 0, paint);
        //绘制ROUND类型的点
        paint.setStrokeCap(Paint.Cap.ROUND);
        canvas.drawPoint(pointDeltaX, 0, paint);
        //绘制SQUARE类型的点
        paint.setStrokeCap(Paint.Cap.SQUARE);
        canvas.drawPoint(pointDeltaX * 2, 0, paint);

        /*---------------------------绘制直线--------------------------*/
        canvas.translate(0, deltaY + smallDeltaY);
        //设置画笔线宽
        paint.setStrokeWidth(5 * density);
        canvas.drawLine(0, 0, canvasWidth, 0, paint);

        /*---------------------------绘制弧线--------------------------*/
        canvas.translate(0, deltaY + smallDeltaY);
        paint.setStyle(Paint.Style.STROKE);
        RectF arcRecF = new RectF();
        arcRecF.left = 0;
        arcRecF.top = 0;
        arcRecF.right = deltaY * 2;
        arcRecF.bottom = deltaY;
        canvas.drawArc(arcRecF, 225, 135, true, paint);

        /*---------------------------绘制矩形--------------------------*/
        canvas.translate(0, deltaY + smallDeltaY);
        paint.setStyle(Paint.Style.FILL);
        canvas.drawRect(0, 0, canvasWidth / 2, deltaY / 2, paint);

        /*---------------------------绘制椭圆面--------------------------*/
        canvas.translate(0, deltaY + smallDeltaY);
        RectF ovalRecF = new RectF();
        ovalRecF.left = 0;
        ovalRecF.top = 0;
        ovalRecF.right = deltaY * 2;
        ovalRecF.bottom = deltaY;
        canvas.drawOval(ovalRecF, paint);

        /*---------------------------绘制Bitmap--------------------------*/
        if(bitmap != null){
            canvas.translate(0, deltaY + smallDeltaY);
            canvas.drawBitmap(bitmap, 0, 0, paint);
        }
    }

对以上代码进行简单说明:

  1. 首先用Canvas的drawText方法绘制了文本,见上图中第一行绘制的文本。

  2. 然后用Canvas的drawPoint方法绘制了点,注意,我分别用BUTT、ROUND、SQUARE作为strokeCap绘制了三个点,并且特意将线宽设置的很大,以便我们容易观察, 见上图中第二行绘制的三个点图形。

  3. 然后用Canvas的drawLine方法绘制了一条线段,也让线段具有一定的线宽,见上图中第三行绘制的线段图形。

  4. 然后用Canvas的drawArc方法绘制了一个扇形模样的椭圆弧,也具有一定的线宽,见上图中第四行绘制的弧线图形。

  5. 然后用Canvas的drawRect方法绘制了一个矩形,见上图中第五行绘制的图形。

  6. 然后用Canvas的drawOval方法绘制了一个椭圆,见上图中第六行绘制的图形。

  7. 最后用Canvas的drawBitmap方法绘制一个Bitmap,见上图中最后一行绘制的图形。

绘制不同类型的图形的目的是为了让大家更好的观察不同类型的图形是如何受不同MaskFilter影响的。上图就是在给画笔Paint设置的MaskFilter为null的情况下绘制的效果,即我们正常绘制图形的效果。


BlurMaskFilter

选中BlurMaskFilter时,会创建模糊阴影效果,选择的style不同的话,效果不同,模糊半径的大小也影响效果,其效果如下所示:
这里写图片描述

BlurMaskFilter的构造函数签名是:

BlurMaskFilter(float radius, BlurMaskFilter.Blur style)

下面对上面两个参数进行解释:

  • radius
    第一个参数radius是float类型,表示是Blur Radius,即阴影的模糊半径,如上图所示,其值越大表示图形绘制出来越模糊,其值越小图形越清晰。

  • style
    第二个参数style是枚举BlurMaskFilter.Blur类型,阴影按细节来分主要分为内阴影和外阴影,内阴影指的是阴影从图形轮廓向内侧扩张,外阴影指的是阴影从图形轮廓向外侧扩张。从阴影角度说,图形的绘制最多由三部分组成: 外阴影 + 图形本身内容 + 内阴影,Blur枚举有四种值:

    • NORMAL
      当style为NORMAL时,会同时绘制图形本身内容+内阴影+外阴影,即正常阴影效果。
      通常情况下,当我们要想使用阴影效果时,一般选择NORMAL作为style。当阴影模糊半径为0时,相当于没有使用阴影效果,随着阴影模糊半径变大,图形更模糊看不清。并且你还可以发现,无论阴影模糊半径如何变化,用BUTT、SQUARE绘制的点都不受影响,绘制的Bitmap也不受影响。可以仔细观察上图中选中NORMAL时图形的阴影效果。

    • INNER
      当style为INNER时,绘制图形内容本身+内阴影,不绘制外阴影。
      当阴影模糊半径为0时,相当于没有使用阴影效果,随着阴影模糊半径变大,可以发现图形从轮廓线向内颜色变浅。无论阴影模糊半径如何变化,用BUTT、SQUARE绘制的点仍然都不受影响,绘制的Bitmap也不受影响。可以仔细观察上图中选中INNER时图形的阴影效果。

    • OUTER
      当style为OUTER时,不绘制图形内容以及内阴影,只绘制外阴影,即图形轮廓以内完全不绘制,轮廓线以内完全是空白的。
      从界面效果上看就是图形被掏空了,只是有荧光向外扩散。当阴影模糊半径为0时,外阴影也几乎看不到了,此时整个图形也基本看不到,随着阴影模糊半径变大,外阴影逐渐明显。无论阴影模糊半径如何变化,用BUTT、SQUARE绘制的点仍然都不受影响,绘制的Bitmap一直基本上不可见。可以仔细观察上图中选中OUTER时图形的阴影效果。

    • SOLID
      当style为SOLID时,只绘制外阴影和图形内容本身,不绘制内阴影。
      从界面上看,由于不绘制内阴影,所以图形轮廓线内部不会发虚。绘制的外阴影会导致轮廓线向外发虚。当阴影模糊半径为0时,相当于没有阴影效果,随着阴影模糊半径的增大,阴影效果明显。用BUTT、SQUARE绘制的点仍然都不受影响,绘制的Bitmap也不受影响。

小结:

  • 不同的style会影响绘制图形的不同部分。
  • 无论哪种style,用BUTT和SQUARE作为strokeCap绘制的正方形的点都完全不受BlurMaskFilter的影响,不过用ROUND作为strokeCap画出的圆形的点会受到影响。
  • 在NORMAL、INNER、SOLID作为style时,绘制的Bitmap基本完全不受影响,在OUTER作为style时,绘制的Bitmap基本不可见。所以BlurMaskFilter对Bitmap的实际用处不大。

EmbossMaskFilter

可以用EmbossMaskFilter创建浮雕效果的MaskFilter,然后通过Paint的setMaskFilter赋值给画笔Paint。其效果如下图所示:
这里写图片描述

EmbossMaskFilter的构造函数签名如下所示:

public EmbossMaskFilter (float[] direction, float ambient, float specular, float blurRadius)

在讲解以上参数之前,先简单介绍一下EmbossMaskFilter的作用机理。所谓的浮雕效果其实就是模拟光照效果,靠近光的一面显得亮一点,远离光的一面显得暗一点,这样就通过颜色的亮暗营造出浮雕的3D立体效果。如果熟悉OpenGL绘图,可能就会对Phong式光照模型比较了解,一般来说,一个相对完整的光照模型=环境光 + 漫反射 + 镜面反射。EmbossMaskFilter为了简化参数并且突出浮雕效果,就把漫反射给去掉了,所以EmbossMaskFilter所使用的光照模型就只有环境光和镜面反射了。

下面对各个参数进行讲解:

  • direction
    direction是一个float类型的数组,表示光线的方向,是个向量,包含三个值,分别是x分量、y分量、z分量,这三个值的绝对大小并不重要,因为direction最后在真正被Android使用时会被归一化成一个单位向量,即变成长度为1的向量。direction中的坐标是在所画图形的右手坐标系中定义的,如下图所示:
    这里写图片描述
    如果对OpenGL开发熟悉的话肯定知道右手坐标系。我们以画椭圆为例,椭圆的中心就是这个三维坐标系的中心点。x正半轴从中心点水平向右,y正半轴从中心点垂直向上,由于图形在Android屏幕内,所以x和y轴都在Android屏幕的平面内。z轴经过椭圆的中心点垂直于屏幕,即同时垂直于x轴和y轴,并且其z正半轴的方向为从屏幕穿过面向我们,即z正半轴的箭头冲向我们。我们可以通过上面的的UI调节direction中的x、y、z的值,从坐标原点(0,0,0)到(x,y,z)的向量即为光照方向。举个例子,当x为1,y为0,z为0时,光照向量为(1,0,0),表示光是恰好沿着从x负半轴指向x正半轴,此时,由于光照从x负半轴照向x正半轴,所以椭圆上处于x负半轴的地方离光照近,被照亮,即椭圆的左侧被照亮,处于x正半轴的地方则显得较暗,则椭圆的右侧则比较暗。大家可以自己调解下参数感受一下,如下图所示:

    这里写图片描述

  • ambient
    ambient表示环境光因子,float类型,取值是0到1,值越接近于0,环境光越暗,值越接近于1,环境光越亮。

  • specular
    specular表示镜面反射因子,float类型,取值也是0到1。镜面反射就是模拟像镜子一样的高光反射,值越接近于0,镜面反射越强,被光照照射到的地方更容易出现很白很亮的状态,即高光效果。

  • blurRadius
    blurRadius表示模糊半径,是float类型,其值越大,模糊效果越明显。


注意

最后需要注意的是,由于在GPU硬件加速模式下,Paint的setMaskFilter不被GPU支持,所以为了能够正常使用setMaskFilter方法,我们需要将我们要绘制的View使用软件渲染模式,具体可参见博文《Android中GPU硬件加速控制及其在2D图形绘制上的局限》。相关代码如下所示:

//为了确保画笔的setMaskFilter能供起效,我们需要对MyView禁用掉GPU硬件加速
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
    //View从API Level 11才加入setLayerType方法
    //设置myView以软件渲染模式绘图
    myView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

代码为Android Studio工程,已经上传到CSDN,点此下载

希望本文对大家使用MaskFilter有所帮助!

相关阅读:
《我的Android博文整理汇总》
《Android中Canvas绘图基础详解(附源码下载)》
《Android中GPU硬件加速控制及其在2D图形绘制上的局限》

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值