自定义View02 - Paint

对于画笔我们想到的就是画笔对颜色的设置,还能写文字.
下面就是大概的一些常见的paint设置API,

总的来说分成以下几种

  1. 设置颜色,透明度,反锯齿等
  2. 设置粗细,PathEffect(CornerPathEffect:将线段的锐角变成顺滑的圆角)
  3. 设置ColorFilter,MaskFilter
  4. 设置Shader
  5. 设置文字的各种相关效果(下划线,放大,对齐等)
  6. 其他

下面就是着重找几个常用的使用一下:

setStyle

笔的样式分三种FILL|STROKE|FILL_AND_STROKE

  private val mRedPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
        style = Paint.Style.FILL
        color = Color.RED
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        testStyle(canvas)
    }

    private fun testStyle(canvas: Canvas) {
        val rect = Rect(0, 0, 100, 100)
        mRedPaint.strokeWidth = 15f
        canvas.save()

        mRedPaint.style = Paint.Style.FILL
        canvas.translate(0f, 100F)
        canvas.drawRect(rect, mRedPaint)

        canvas.translate(150F, 0F)
        mRedPaint.style = Paint.Style.STROKE
        canvas.drawRect(rect, mRedPaint)

        canvas.translate(150F, 0F)
        mRedPaint.style = Paint.Style.FILL_AND_STROKE
        canvas.drawRect(rect, mRedPaint)

        canvas.restore()
        mRedPaint.strokeWidth = 40F
    }

表现上看就是

  1. FILL是填充内部,但是不算paint的线的宽度
  2. STROKE是只进行描边
  3. FILL_AND_STROKE就是前两个相加

setStrokeCap - 笔头的样式

    private fun testOfCap(canvas: Canvas) {
        mRedPaint.strokeWidth = 20F
        canvas.save()
        canvas.translate(150F, 200F)
        //线帽测试:
        mRedPaint.strokeCap = Paint.Cap.BUTT//无头(默认)
        canvas.drawLine(0F, 0F, 0F, 200F, mRedPaint)
        canvas.translate(50F, 0F)
        mRedPaint.strokeCap = Paint.Cap.ROUND//圆头
        canvas.drawLine(0F, 0F, 0F, 200F, mRedPaint)
        canvas.translate(50F, 0F)
        mRedPaint.strokeCap = Paint.Cap.SQUARE//方头
        canvas.drawLine(0F, 0F, 0F, 200F, mRedPaint)
        canvas.restore()
    }


setStrokeJoin - 线交角的样式

/**
     * 角型测试:Paint.Join.BEVEL、Paint.Join.ROUND、Paint.Join.MITER
     *
     * @param canvas
     */
      private fun testOfJoin(canvas: Canvas) {
        mRedPaint.setStyle(Paint.Style.STROKE)
        mRedPaint.setStrokeWidth(40F)
        val path = Path()
        path.moveTo(30F, 0F)
        path.lineTo(0F, 100F)
        path.lineTo(100F, 100F)
        path.lineTo(100F, 200F)

        canvas.save()
        canvas.translate(100F, 100F)
        mRedPaint.strokeJoin = Paint.Join.BEVEL//直线(默认)
        canvas.drawPath(path, mRedPaint)

        canvas.translate(150F, 0F)
        mRedPaint.strokeJoin = Paint.Join.ROUND//圆角
        canvas.drawPath(path, mRedPaint)

        canvas.translate(150F, 0F)
        mRedPaint.strokeJoin = Paint.Join.MITER//锐角
        canvas.drawPath(path, mRedPaint)
        canvas.restore()
    }

setShader(Shader shader) - 用来处理颜色渐变

Shader的模式

  1. LinearGradient - 线性渐变
  2. SweepGradient - 创建360度颜色旋转渐变效果
  3. RadialGradient - 中心向四周发散的辐射渐变效果
  4. BitmapShader - 位图着色,可以利用该类做各种各样的图片裁剪。
  5. ComposeShader - 组合Shader,可以将连个Shader组合在一起

LinearGradient - 线性渐变

LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, Shader.TileMode tile)
LinearGradient(float x0, float y0, float x1, float y1, int[] colors, float[] positions, Shader.TileMode tile)

LinearGradient是用来创建线性渐变效果的,它是沿着某条直线的方向渐变的,坐标(x0,y0)就是这条渐变直线的起点,坐标(x1,y1)就是这条渐变直线的终点。需要说明的是,坐标(x0,y0)和坐标(x1,y1)都是Canvas绘图坐标系中的坐标。color0和color1分别表示了渐变的起始颜色和终止颜色。与BitmapShader类似,LinearGradient也支持TileMode,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。


    @RequiresApi(Build.VERSION_CODES.S)
    private fun setShader(canvas: Canvas) {
        val colorStart = Color.parseColor("#FF0000")
        val colorEnd = Color.parseColor("#00FF00")
        canvas.save();
        canvas.translate(0f, 0F);
        mRedPaint.setStyle(Paint.Style.FILL);

        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.MIRROR
            )
        )
        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.CLAMP
            )
        )
        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.REPEAT
            )
        )
        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.DECAL
            )
        )

        for (i in shaderEffects.indices) {
            mRedPaint.setShader(shaderEffects[i])
            canvas.translate(0f, 200f)
            canvas.drawRect(0F, 0F, 800F, 100F, mRedPaint);
        }


    }

从上往下分别是
Shader.TileMode.MIRROR,如果没有颜色选定了,那就镜像设置一遍
Shader.TileMode.CLAMP,如果没有颜色选定了,那就复制边缘的颜色
Shader.TileMode.REPEAT,如果没有颜色选定了,那就是重复开始一遍
Shader.TileMode.DECAL,如果没有颜色选定了,那就没有了,后面透明(target version = 31)

然后我们再看一下LinearGTradient 的(x,y)的区别

@RequiresApi(Build.VERSION_CODES.S)
    private fun setShader(canvas: Canvas) {
        val colorStart = Color.parseColor("#FF0000")
        val colorEnd = Color.parseColor("#00FF00")
        canvas.save();
        canvas.translate(0f, 0F);
        mRedPaint.setStyle(Paint.Style.FILL);

        shaderEffects.add(
            LinearGradient(
                0F, 50F, 400F, 50F, colorStart, colorEnd, Shader.TileMode.MIRROR
            )
        )

        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.MIRROR
            )
        )

        shaderEffects.add(
            LinearGradient(
                0F, 0F, 800F, 100F, colorStart, colorEnd, Shader.TileMode.MIRROR
            )
        )


        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.CLAMP
            )
        )
        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.REPEAT
            )
        )
        shaderEffects.add(
            LinearGradient(
                0F, 0F, 400F, 100F, colorStart, colorEnd, Shader.TileMode.DECAL
            )
        )

        for (i in shaderEffects.indices) {
            mRedPaint.setShader(shaderEffects[i])
            canvas.translate(0f, 200f)
            canvas.drawRect(0F, 0F, 800F, 100F, mRedPaint);
        }


    }

图中一个长方形的坐标是(0,0,800,100),
所以LinearGradient的坐标如果是(0F, 50F, 400F, 50F),说明start的光照点在左中的位置,end的光照点在右中的位置,如红点所示;
(0F, 50F, 400F, 100F)的start光照点在左中,end的光照点在正中间
(0F, 0F, 800F, 100F)的start的光照点在左上,end的光照点在右下
跟着代码看图就能理解.

另外:
在LinearGradient的第二个构造函数中可以通过参数colors传入多个颜色值进去,这样就会用colors数组中指定的颜色值一起进行颜色线性插值。还可以指定positions数组,该数组中每一个position对应colors数组中每个颜色在线段中的相对位置,position取值范围为[0,1],0表示起始位置,1表示终止位置。如果positions数组为null,那么Android会自动为colors设置等间距的位置。

int[] colors = new int[]{
        Color.parseColor("#F60C0C"),//红
        Color.parseColor("#F3B913"),//橙
        Color.parseColor("#E7F716"),//黄
        Color.parseColor("#3DF30B"),//绿
        Color.parseColor("#0DF6EF"),//青
        Color.parseColor("#0829FB"),//蓝
        Color.parseColor("#B709F4"),//紫
};
float[] pos = new float[]{
        1.f / 7, 2.f / 7, 3.f / 7, 4.f / 7, 5.f / 7, 6.f / 7, 1
};

canvas.translate(0, 150);
mRedPaint.setShader(
        new LinearGradient(
                -300, 0, 300, 0,
                colors, pos,
                Shader.TileMode.CLAMP

        ));
canvas.drawRect(-400, -200, 400, -100, mRedPaint);

效果如下
166ed3022ce68331_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0

SweepGradient - 圆形扫面渐变

SweepGradient不支持TileMode参数
坐标(cx,cy)决定了中心点的位置,会绕着该中心点进行360度旋转。color0表示的是起点的颜色位置,color1表示的是终点的颜色位置。

    private fun setSweepShader(canvas: Canvas) {
        val colorStart = Color.parseColor("#FF0000")
        val colorEnd = Color.parseColor("#00FF00")
        canvas.save();
        canvas.translate(200f, 200F);

        mRedPaint.setStyle(Paint.Style.FILL);

        shaderEffects.add(
            SweepGradient(
                0F, 0F, colorStart, colorEnd
            )
        )

        for (i in shaderEffects.indices) {
            mRedPaint.setShader(shaderEffects[i])
            canvas.drawCircle(0F, 0F, 200F, mRedPaint);//200f是半径
        }


    }

在SweepGradient的第二个构造函数中,我们可以传入一个colors颜色数组,这样Android就会根据传入的颜色数组一起进行颜色插值。还可以指定positions数组,该数组中每一个position对应colors数组中每个颜色在360度中的相对位置,position取值范围为[0,1],0和1都表示3点钟位置,0.25表示6点钟位置,0.5表示9点钟位置,0.75表示12点钟位置,诸如此类。如果positions数组为null,那么Android会自动为colors设置等间距的位置。

    private fun setSweepShader(canvas: Canvas) {
        val colorStart = Color.parseColor("#FF0000")
        val colorEnd = Color.parseColor("#00FF00")
        canvas.save();
        canvas.translate(200f, 200F)

        mRedPaint.setStyle(Paint.Style.FILL)

        val colors = intArrayOf(Color.RED, Color.GREEN, Color.BLUE,Color.WHITE)
        val positions = floatArrayOf(0f, 0.5f, 0.75f,1f)

        shaderEffects.add(
            SweepGradient(
                0F, 0F, colors, positions
            )
        )

        for (i in shaderEffects.indices) {
            mRedPaint.setShader(shaderEffects[i])
            canvas.drawCircle(0F, 0F, 200F, mRedPaint);
        }
    }

RadialGradient - 从中心向四周发散的辐射

我们可以用RadialGradient创建从中心向四周发散的辐射渐变效果,其有两个构造函数:

RadialGradient(float centerX, float centerY, float radius, int centerColor, int edgeColor, Shader.TileMode tileMode)
RadialGradient(float centerX, float centerY, float radius, int[] colors, float[] stops, Shader.TileMode tileMode)

这两个构造函数和LinearGradient的两个构造函数很类似,我们此处还是重点讲解第一个构造函数,在此基础上理解第二个构造函数就很简单了。

RadialGradient是用来创建从中心向四周发散的辐射渐变效果的,所以我们需要在其构造函数中传入一些圆的参数,坐标(centerX,centerY)是圆心,即起始的中心颜色的位置,radius确定了圆的半径,在圆的半径处的颜色是edgeColor,这样就确定了当位置从圆心移向圆的轮廓时,颜色逐渐从centerColor渐变到edgeColor。RadialGradient也支持TileMode参数,有以下三个取值:CLAMP 、REPEAT 和 MIRROR。

    private fun setRadialShader(canvas: Canvas) {
        val colorStart = Color.parseColor("#FF0000")
        val colorEnd = Color.parseColor("#00FF00")
        canvas.save();
        canvas.translate(0f, 0F);
        mRedPaint.setStyle(Paint.Style.FILL);

        shaderEffects.add(
            RadialGradient(
                100F, 100F, 50F, colorStart, colorEnd, Shader.TileMode.MIRROR
            )
        )

        shaderEffects.add(
            RadialGradient(
                100F, 100F, 50F, colorStart, colorEnd, Shader.TileMode.REPEAT
            )
        )

        shaderEffects.add(
            RadialGradient(
                100F, 100F, 50F, colorStart, colorEnd, Shader.TileMode.CLAMP
            )
        )


        for (i in shaderEffects.indices) {
            mRedPaint.setShader(shaderEffects[i])
            canvas.translate(0f, 250f)
            canvas.drawRect(0F, 0F,200f, 200F, mRedPaint);
        }


    }

没什么好说的,跟LinearGradient差不太多

在RadialGradient的第二个构造函数中可以通过参数colors传入多个颜色值进去,这样就会用colors数组中指定的颜色值一起进行颜色线性插值。还可以指定stops数组,该数组中每一个stop对应colors数组中每个颜色在半径中的相对位置,stop取值范围为[0,1],0表示圆心位置,1表示圆周位置。如果stops数组为null,那么Android会自动为colors设置等间距的位置。

BitmapShader - 位图着色,可以利用该类做各种各样的图片裁剪。

BitmapShader(Bitmap bitmap, Shader.TileMode tileX, Shader.TileMode tileY)
第一个参数是Bitmap对象,该Bitmap决定了用什么图片对绘制的图形进行贴图。
第二个参数和第三个参数都是Shader.TileMode类型的枚举值,表示的是对x轴上怎么排列,和对y轴上怎么排列
用Bitmap对绘制的图形进行渲染着色,其实就是用图片对图形进行贴图。
一样分成三个:CLAMP 、REPEAT 和 MIRROR。

CLAMP表示,当所画图形的尺寸大于Bitmap的尺寸的时候,会用Bitmap四边的颜色填充剩余空间。
我们有一个Bitmap,如下所示:

CLAMP如下:

REPEAT表示,当我们绘制的图形尺寸大于Bitmap尺寸时,会用Bitmap重复平铺整个绘制的区域。REPEAT如下:

与REPEAT类似,当绘制的图形尺寸大于Bitmap尺寸时,MIRROR也会用Bitmap重复平铺整个绘图区域,与REPEAT不同的是,两个相邻的Bitmap互为镜像。
MIRROR如下:

使用BitmapShader给文字的图片上底色

    private fun setBitmapShader(canvas: Canvas) {
        //加载图片,生成图片着色器
        val bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner);
        val bs = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
        mRedPaint.setShader(bs);
        mRedPaint.setTextSize(150F);
        mRedPaint.setStrokeWidth(10F);
        mRedPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawText("周杰伦龍龍龍", 0F, 500F, mRedPaint);
    }

这个没什么好说的,就是对绘制的图形上图片的底色,这里就是用了drawText,其实如果最后一句改成 canvas.drawCircle( 100F, 100F, 50f, mRedPaint)就是下面比较常见的情况

ComposeShader - 组合Shader,可以将连个Shader组合在一起

//todo

ComposeShader,顾名思义,就是混合Shader的意思,它可以将两个Shader按照一定的Xfermode组合起来。

ComposeShader有两个构造函数,如下所示:

ComposeShader(Shader shaderA, Shader shaderB, Xfermode mode)
ComposeShader(Shader shaderA, Shader shaderB, PorterDuff.Mode mode)

类android.graphics.PorterDuffXfermode继承自android.graphics.Xfermode,所以上面的第二个构造函数可以看做是第一个构造函数的特例。我们主要讲解第二个,二者大同小异。

有一点需要说明,ComposeShader这个类不是必须的,也就是我们不用这个类也能创造对应的效果,它类似于一个助手类,为我们实现某种效果提供了方便,下面举例说明。构造函数中的shaderA对应着目标像素,shaderB对应着源像素。所以mode应用时将把从shaderA(ComposeShader 第一个参数)中获取的结果作为目标图像,shaderB(ComposeShader第二个参数)中获取的结果作为源图像,进行混合叠加。如果相交的两个图像大小不同,就会存在不想交的部分,不想交的部分是不会相互影响的

我们的图片中间的❤形区域是纯白色,该区域的像素颜色值ARGB分量是(255,255,255,255)。❤形区域以外的区域是纯透明的,该区域的像素颜色值ARGB分量是(0,0,0,0)。

为了使用Xfermode,我们将绘图的代码放到了canvas.saveLayer()和canvas.restore()之间,对此有疑问的同学可以参见我上述提到的博文。canvas.saveLayer()会创建一个新的绘图图层,而且该图层是全透明的,我们后面的代码都是绘制到这个图层上,而不是直接绘制到Canvas上。

我们用上述Bitmap创建了一个BitmapShader,并将其绑定到画笔Paint中。当我们用canvas.drawRect()绘制矩形时,就会用该BitmapShader填充,此时的效果应该是在新创建的layer上绘制了一个白色的心形。

然后我们创建了一个PorterDuffXfermode的实例,并通过paint.setXfermode()将其绑定到画笔paint上。其中PorterDuffXfermode的mode类型为MULTIPLY。MULTIPLY的意思是将源像素的ARGB四个分量分别与目标像素对应的ARGB四个分量相乘,将相乘的结果作为混合后的像素。此处进行相乘时,ARGB四个分量都已经从[0, 255]的区间归一化到[0.0, 1.0]的区间。

然后我们创建了一个LinearGradient,用以实现颜色线性渐变效果。颜色从左上角的绿色渐变到右下角的蓝色。然后我们通过paint.setShader()方法将其绑定到画笔paint的shader上。

后面我们再次调用canvas.drawRect()绘制同样大小的一个矩形。在绘制时,我们的画笔已经同时绑定了Xfermode和Shader。首先canvas会用LinearGradient绘制一个具有渐变色的矩形区域。然后根据画笔设置的PorterDuff.Mode.MULTIPLY类型,将那些由渐变色填充的矩形区域中的像素与我们在第3步中绘制的心形图片中的像素颜色进行相乘混合。渐变色填充的矩形区域中的像素是源像素,第3步中绘制的心形图片中的像素是目标像素。目标像素中❤形区域是纯白色的,其像素颜色是(255,255,255,255),归一化后的颜色是(1,1,1,1),对应位置的源像素中的ARGB颜色分量与其相乘,最终的颜色还是源像素的颜色,即心形区域被源像素着上了渐变色。目标像素中❤形区域以外的颜色是纯透明的,颜色是(0,0,0,0),对应位置的源像素中的ARGB颜色分量与其相乘,最终的颜色还是目标像素中的(0,0,0,0),即心形区域以外没有被着色,依旧呈现透明色。

最后通过调用canvas.restore()方法将新创建的layer绘制到Canvas上去,这样我们就看到最终的效果了。

下面我们看看如和用ComposeShader实现上述效果,代码如下所示:

int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
//创建BitmapShader,用以绘制❤形
BitmapShader bitmapShader = new BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
//创建LinearGradient,用以产生从左上角到右下角的颜色渐变效果
LinearGradient linearGradient = new LinearGradient(0, 0, bitmapWidth, bitmapHeight, Color.GREEN, Color.BLUE, Shader.TileMode.CLAMP);
//bitmapShader对应目标像素,linearGradient对应源像素,像素颜色混合采用MULTIPLY模式
ComposeShader composeShader = new ComposeShader(bitmapShader, linearGradient, PorterDuff.Mode.MULTIPLY);
//将组合的composeShader作为画笔paint绘图所使用的shader
paint.setShader(composeShader);
//用composeShader绘制矩形区域
canvas.drawRect(0, 0, bitmapWidth, bitmapHeight, paint);

用ComposeShader实现的效果与上图相同,我就不再贴图了。我们可以看到,使用ComposeShader之后,实现相同的效果时,代码量明显减少了,而且我们也不需要将绘图代码放到canvas.saveLayer()和canvas.restore()之间了。

根据上面的示例,我们可以得出如下结论:
假设我们定义了两个Shader的变量,shaderA和shaderB,并分别对这两个Shader进行了实例化。
可以使用ComposeShader将二者组合使用,基本代码如下所示:

ComposeShader composeShader = new ComposeShader(shaderA, shaderB, porterDuffMode);
paint.setShader(composeShader);
canvas.drawXXX(..., paint);

上述代码等价于下面的代码片段:

canvas.saveLayer(left, top, right, bottom, null, Canvas.ALL_SAVE_FLAG);
paint.setShader(shaderA);
canvas.drawXXX(..., paint);
paint.setXfermode(new PorterDuffXfermode(mode));
paint.setShader(shaderB);
canvas.drawXXX(..., paint);
paint.setXfermode(null);
canvas.restore();

此处所说的以上两个代码片段等价的前提是,两个代码片段中的canvas.drawXXX(…, paint)方法中调用的drawXXX方法相同,并且里面传入的参数都相同,例如我们之前两段心形代码示例中都调用drawRect()方法且绘制的矩形的位置及尺寸都相同。

setColorFilter(ColorFilter filter) - 用来基于颜色进行过滤处理

ColorFilter的使用

颜色过滤器可以将颜色按照一定的规则输出,常见于各种滤镜效果

LightingColorFilter - 模拟光照效果

LightingColorFilter(int mul, int add)
mul 用来乘以原图的 RPG 值
add 添加到前面得出的结果上
计算方法:(RGB 值 * mul + Add) % 255

    private fun setLightingColorFilter(canvas: Canvas) {
        val mainBitmap =
            BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_7);
        mRedPaint.setStyle(Paint.Style.FILL);
        canvas.save()
        mRedPaint.setColorFilter(
            LightingColorFilter(
                Color.parseColor("#FF0000"),//红
                Color.parseColor("#0000ff")//蓝
            )
        );
        canvas.drawBitmap(mainBitmap, 0f, 0f, mRedPaint);

        canvas.translate(350f, 0f);
        mRedPaint.setColorFilter(
            LightingColorFilter(
                Color.parseColor("#FF0000"),//红
                Color.parseColor("#00ff00")//绿
            )
        );
        canvas.drawBitmap(mainBitmap, 0f, 0F, mRedPaint);

        canvas.translate(350f, 0f);
        mRedPaint.setColorFilter(
            LightingColorFilter(
                Color.parseColor("#FF0000"),//红
                Color.parseColor("#000000")//黑
            )
        );
        canvas.drawBitmap(mainBitmap, 0F, 0F, mRedPaint);
        canvas.restore();


    }

LightingColorFilter(0xFFFFFFFF, 0x00000000)的时候原图是不会有任何改变的

如果我们想增加红色的值,那么LightingColorFilter(0xFFFFFFFF, 0x00XX0000)就好,其中XX取值为00至FF
如下:

        mRedPaint.setColorFilter(
            LightingColorFilter(
                Color.parseColor("#FFFFFF"),//红
                Color.parseColor("#00550000")//蓝
            )
        )

PorterDuffColorFilter - 模拟颜色混合效果

    private fun setPorterDuffColorFilter(canvas: Canvas) {
        val mainBitmap =
            BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_7);
        mRedPaint.setStyle(Paint.Style.FILL);
        canvas.save()
        mRedPaint.setColorFilter(
            PorterDuffColorFilter(
                Color.parseColor("#FF0000"),//红
                PorterDuff.Mode.DARKEN
            )
        )
        canvas.drawBitmap(mainBitmap, 0f, 0f, mRedPaint);

        canvas.translate(350f, 0f);
        mRedPaint.setColorFilter(
            PorterDuffColorFilter(
                Color.parseColor("#FF0000"),//红
                PorterDuff.Mode.LIGHTEN,
            )
        );
        canvas.drawBitmap(mainBitmap, 0f, 0F, mRedPaint);

        canvas.translate(350f, 0f);
        mRedPaint.setColorFilter(
            PorterDuffColorFilter(
                Color.parseColor("#FF0000"),//红
                PorterDuff.Mode.OVERLAY,

            )
        );
        canvas.drawBitmap(mainBitmap, 0F, 0F, mRedPaint);
        canvas.restore();

    }

ColorMatrixColorFilter - 颜色矩阵过滤

很多应用是有滤镜效果的,这里的MatrixColor就能完成滤镜的效果,通常我们见过的常见的滤镜就是预设的矩阵值

{
    // 黑白
    val colormatrix_heibai = floatArrayOf(
        0.8f, 1.6f, 0.2f, 0f,
        -163.9f, 0.8f, 1.6f, 0.2f, 0f, -163.9f, 0.8f, 1.6f, 0.2f, 0f,
        -163.9f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 怀旧
    val colormatrix_huaijiu = floatArrayOf(
        0.2f, 0.5f, 0.1f, 0f,
        40.8f, 0.2f, 0.5f, 0.1f, 0f, 40.8f, 0.2f, 0.5f, 0.1f, 0f, 40.8f, 0f,
        0f, 0f, 1f, 0f
    )

    // 哥特
    val colormatrix_gete = floatArrayOf(
        1.9f, -0.3f, -0.2f, 0f,
        -87.0f, -0.2f, 1.7f, -0.1f, 0f, -87.0f, -0.1f, -0.6f, 2.0f, 0f,
        -87.0f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 淡雅
    val colormatrix_danya = floatArrayOf(
        0.6f, 0.3f, 0.1f, 0f,
        73.3f, 0.2f, 0.7f, 0.1f, 0f, 73.3f, 0.2f, 0.3f, 0.4f, 0f, 73.3f, 0f,
        0f, 0f, 1.0f, 0f
    )

    // 蓝调
    val colormatrix_landiao = floatArrayOf(
        2.1f, -1.4f, 0.6f,
        0.0f, -71.0f, -0.3f, 2.0f, -0.3f, 0.0f, -71.0f, -1.1f, -0.2f, 2.6f,
        0.0f, -71.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
    )

    // 光晕
    val colormatrix_guangyun = floatArrayOf(
        0.9f, 0f, 0f, 0f, 64.9f,
        0f, 0.9f, 0f, 0f, 64.9f, 0f, 0f, 0.9f, 0f, 64.9f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 梦幻
    val colormatrix_menghuan = floatArrayOf(
        0.8f, 0.3f, 0.1f,
        0.0f, 46.5f, 0.1f, 0.9f, 0.0f, 0.0f, 46.5f, 0.1f, 0.3f, 0.7f, 0.0f,
        46.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
    )

    // 酒红
    val colormatrix_jiuhong = floatArrayOf(
        1.2f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.9f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f,
        0f, 0f, 0f, 1.0f, 0f
    )

    // 胶片
    val colormatrix_fanse = floatArrayOf(
        -1.0f, 0.0f, 0.0f, 0.0f,
        255.0f, 0.0f, -1.0f, 0.0f, 0.0f, 255.0f, 0.0f, 0.0f, -1.0f, 0.0f,
        255.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
    )

    // 湖光掠影
    val colormatrix_huguang = floatArrayOf(
        0.8f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.9f, 0.0f, 0.0f,
        0f, 0f, 0f, 1.0f, 0f
    )

    // 褐片
    val colormatrix_hepian = floatArrayOf(
        1.0f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.8f, 0.0f, 0.0f,
        0f, 0f, 0f, 1.0f, 0f
    )

    // 复古
    val colormatrix_fugu = floatArrayOf(
        0.9f, 0.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.8f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f,
        0f, 0f, 0f, 1.0f, 0f
    )

    // 泛黄
    val colormatrix_huan_huang = floatArrayOf(
        1.0f, 0.0f, 0.0f,
        0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f,
        0.0f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 传统
    val colormatrix_chuan_tong = floatArrayOf(
        1.0f, 0.0f, 0.0f, 0f,
        -10f, 0.0f, 1.0f, 0.0f, 0f, -10f, 0.0f, 0.0f, 1.0f, 0f, -10f, 0f, 0f,
        0f, 1f, 0f
    )

    // 胶片2
    val colormatrix_jiao_pian = floatArrayOf(
        0.71f, 0.2f, 0.0f,
        0.0f, 60.0f, 0.0f, 0.94f, 0.0f, 0.0f, 60.0f, 0.0f, 0.0f, 0.62f,
        0.0f, 60.0f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 锐色
    val colormatrix_ruise = floatArrayOf(
        4.8f, -1.0f, -0.1f, 0f,
        -388.4f, -0.5f, 4.4f, -0.1f, 0f, -388.4f, -0.5f, -1.0f, 5.2f, 0f,
        -388.4f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 清宁
    val colormatrix_qingning = floatArrayOf(
        0.9f, 0f, 0f, 0f, 0f, 0f,
        1.1f, 0f, 0f, 0f, 0f, 0f, 0.9f, 0f, 0f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 浪漫
    val colormatrix_langman = floatArrayOf(
        0.9f, 0f, 0f, 0f, 63.0f,
        0f, 0.9f, 0f, 0f, 63.0f, 0f, 0f, 0.9f, 0f, 63.0f, 0f, 0f, 0f, 1.0f, 0f
    )

    // 夜色
    val colormatrix_yese = floatArrayOf(
        1.0f, 0.0f, 0.0f, 0.0f,
        -66.6f, 0.0f, 1.1f, 0.0f, 0.0f, -66.6f, 0.0f, 0.0f, 1.0f, 0.0f,
        -66.6f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f
    )
}
    private fun setColorMatrixColorFilter(canvas: Canvas) {

        canvas.save();
        canvas.translate(0f, 0F);
        mRedPaint.setStyle(Paint.Style.FILL);

        val mainBitmap =
            BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_5);
        mRedPaint.setStyle(Paint.Style.FILL);
        canvas.save()

        for (floats in FloatMarixImg.getList()) {
            val colorMatrix = ColorMatrix(floats)
            mRedPaint.colorFilter = ColorMatrixColorFilter(colorMatrix)
            canvas.drawBitmap(mainBitmap, 0f, 0f, mRedPaint);
            canvas.translate(0f, 250f)
            canvas.drawRect(0F, 0F, 200f, 200F, mRedPaint);
        }
    }

产生的图像就如下:

关于矩阵Color因为平时没有用到,所以没有更多的研究

setXfermode(Xfermode xfermode) - 用来处理源图像和 View 已有内容的关系

此处对Xfermode做一下简单介绍,Xfermode可以用于实现新绘制的像素与Canvas上对应位置已有的像素按照混合规则进行颜色混合。Xfermode有三个子类:AvoidXfermode, PixelXorXfermode和PorterDuffXfermode,其中前两个类现在被Android废弃了,现在主要用的是PorterDuffXfermode。PorterDuffXfermode的构造函数需要指定PorterDuff.Mode的类型。

在用Android中的Canvas进行绘图时,可以通过使用PorterDuffXfermode将所绘制的图形的像素与Canvas中对应位置的像素按照一定规则进行混合,形成新的像素值,从而更新Canvas中最终的像素颜色值,这样会创建很多有趣的效果。当使用PorterDuffXfermode时,需要将将其作为参数传给Paint.setXfermode(Xfermode xfermode)方法,这样在用该画笔paint进行绘图时,Android就会使用传入的PorterDuffXfermode,如果不想再使用Xfermode,那么可以执行Paint.setXfermode(null).

我们知道,在使用Xfermode的时候,存在目标像素DST和源像素SRC之说。源像素指的是将要向Canvas上绘制的像素,目标像素指的是源像素在Canvas上对应位置已经存在的像素。

Android 中PorterDuff.Mode模式包括:

Mode.CLEAR;
Mode.SRC;
Mode.DST;
Mode.SRC_OVER;
Mode.DST_OVER;
Mode.SRC_IN;
Mode.DST_IN;
Mode.SRC_OUT;
Mode.DST_OUT;
Mode.SRC_ATOP;
Mode.DST_ATOP;
Mode.XOR;
Mode.DARKEN;
Mode.LIGHTEN;
Mode.MULTIPLY;
Mode.SCREEN;
Mode.ADD;
Mode.OVERLAY;

Xfermode的使用

//todo

Paint对文字的处理

Paint里有大量方法来设置文字的绘制属性,事实上文字在Android底层是被当做图片来处理的。

setTextSize(float textSize):设置文字大小
setTypeface(Typeface typeface):设置文字字体
setFakeBoldText(boolean fakeBoldText):是否使用伪粗体(并不是提到size,而是在运行时描粗的)
setTextAlign(Paint.Align align):设置文字对齐方式

    private fun setTextStyle(canvas: Canvas) {
        canvas.save()
        val tempPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        tempPaint.strokeWidth = 4f
        tempPaint.color = Color.BLUE

        canvas.translate(500F, 200F)
        mRedPaint.setTypeface(Typeface.SANS_SERIF)
        mRedPaint.setTextAlign(Paint.Align.LEFT)
        mRedPaint.setTypeface(Typeface.DEFAULT) //字体
        mRedPaint.setTextSize(100F)

        canvas.drawText("SANS_SERIF", 0F, 0F, mRedPaint)
        canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)

        canvas.translate(0F, 150F)
        mRedPaint.setTextAlign(Paint.Align.RIGHT)
        mRedPaint.setTypeface(Typeface.SERIF)
        canvas.drawText("SERIF", 0F, 0F, mRedPaint)
        canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)

        canvas.translate(0F, 150F)
        mRedPaint.setTextAlign(Paint.Align.CENTER)
        mRedPaint.setTypeface(Typeface.MONOSPACE)
        canvas.drawText("MONOSPACE", 0F, 0F, mRedPaint)
        canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)
        canvas.restore()
    }


这三个点就是起始点,这里主要要看Paint.Align对Text的影响

setStrikeThruText(boolean strikeThruText):是否添加删除线

setUnderlineText(boolean underlineText):是否添加下划线

setTextSkewX(float skewX):设置文字倾斜度

setTextScaleX(float scaleX):设置文字横向缩放

setLetterSpacing(float letterSpacing):设置文字间距 Added in API level 21,设置文本字母间距,默认0,负值收紧文本

setFontFeatureSettings(String settings):使用CSS的font-feature-settings的方式来设置文字。

setTextLocale(Locale locale):设置文字Local

setHinting(int mode):设置字体Hinting(微调),过向字体中加入 hinting 信息,让矢量字体在尺寸过小的时候得到针对性的修正,从而提高显示效果。

setSubpixelText(boolean subpixelText):设置次像素级抗锯齿,根据程序所运行的设备的屏幕类型,来进行针对性的次像素级的抗锯齿计算,从而达到更好的抗锯齿效果。

setLinearText 设置线性文本

setShadowLayer(模糊半径,x偏移,y偏移,颜色)

    private fun setTextShadowLayer(canvas: Canvas) {
        canvas.save()
        val tempPaint = Paint(Paint.ANTI_ALIAS_FLAG)
        tempPaint.strokeWidth = 4f
        tempPaint.color = Color.BLUE
        mRedPaint.setShadowLayer(20F, 4F, 4F, Color.parseColor("#000000"));
        canvas.translate(500F, 200F)
        mRedPaint.setTypeface(Typeface.SANS_SERIF)
        mRedPaint.setTypeface(Typeface.DEFAULT) //字体
        mRedPaint.setTextSize(100F)
        canvas.drawText("SANS_SERIF", 0F, 0F, mRedPaint)
        canvas.drawRect(0F, 0F, 10f, 50F, tempPaint)
    }

setAntiAlias (boolean aa)

设置抗锯齿,默认关闭,用来是图像的绘制更加圆润。我们还可以在初始化的时候设置Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);。

setStrokeWidth(float width) - 设置线条粗细

setStrokeMiter(float miter) - 设置 MITER 型拐角的延长线的最大值

setDither(boolean dither)

设置图像的抖动。抖动是指把图像从较高色彩深度(即可用的颜色数)向较低色彩深度的区域绘制时,在图像中有意地插入噪点,通过有规律地扰乱图像来让图像对于肉眼更加真实的做法。当然这个效果旨在低位色的时候比较有用,例如,ARGB_4444 或者 RGB_565,不过现在Android默认的色彩深度都是32位的ARGB_8888,这个方法的效果没有那么明显。

setFilterBitmap(boolean filter)

设置是否使用双线性过滤来绘制 Bitmap .图像在放大绘制的时候,默认使用的是最近邻插值过滤,这种算法简单,但会出现马赛克现象;而如果开启了双线性过滤,就可以让结果图像显得更加平滑。

    private fun setFilterBitmap(canvas: Canvas) {
        val mainBitmap =
            BitmapFactory.decodeResource(getResources(), R.drawable.rank_banner_avatar_5);
        val matrix = Matrix()
        matrix.setScale(5F, 5F);//放大矩阵

        mRedPaint.setFilterBitmap(false);
        canvas.drawBitmap(mainBitmap, matrix, mRedPaint);

        canvas.translate(-500F, -300F);
        mRedPaint.setFilterBitmap(true);
        canvas.drawBitmap(mainBitmap, matrix, mRedPaint);

    }

setPathEffect(PathEffect effect)

设置图形的轮廓效果。Android有六种PathEffect:

  1. CornerPathEffect:将拐角绘制成圆角
  2. DiscretePathEffect:将线条进行随机偏离
  3. DashPathEffect:绘制虚线
  4. PathDashPathEffect:使用指定的Path来绘制虚线
  5. SumPathEffect:组合两个PathEffect,叠加应用
  6. ComposePathEffect:组合两个PathEffect,叠加应用。

简单的直接看这个demo


private void initPath() {
        mPath = new Path();
        mPath.moveTo(10, 50);
        for (int i = 0; i < 20; i++) {
            mPath.lineTo(i * 35, (float) (Math.random() * 100));
        }

        pathEffects[0] = null;
        pathEffects[1] = new CornerPathEffect(20);
        pathEffects[2] = new DashPathEffect(new float[]{20, 10}, mPhase);
        pathEffects[3] = new DiscretePathEffect(5.0f, 10.0f);

        Path path = new Path();
        path.addRect(0, 0, 8, 8, Path.Direction.CCW);
        pathEffects[4] = new PathDashPathEffect(path, 20, mPhase, PathDashPathEffect.Style.ROTATE);

        pathEffects[5] = new ComposePathEffect(pathEffects[2], pathEffects[1]);
        pathEffects[6] = new SumPathEffect(pathEffects[4], pathEffects[3]);

    }

对应的7个就是以下的效果

CornerPathEffect(float radius)

radius 圆角半径


    /**
     * 圆角折线
     *
     * @param canvas
     */
    private fun cornerEffect(canvas: Canvas) {

        // 不断改变mEffectCorner圆角大小可以看到效果
        val mEffectCorner = 20f
        mEffectPaint.setPathEffect(CornerPathEffect(mEffectCorner))


        val path = Path()
        path.moveTo(550F, 550F);
        path.lineTo(900F, 300F);
        path.lineTo(1000F, 550F);
        canvas.drawPath(path, mEffectPaint);
        //蓝色辅助线
        val tempPaint = Paint();
        tempPaint.setStyle(Paint.Style.STROKE);
        tempPaint.setColor(Color.BLUE);
        tempPaint.setStrokeWidth(4F);
        tempPaint.setPathEffect(DashPathEffect(floatArrayOf(20F, 20F), 0F));


        val helpPath = Path();
        helpPath.moveTo(550F, 550F);
        helpPath.lineTo(900F, 300F);
        helpPath.lineTo(1000F, 550F);
        canvas.drawPath(helpPath, tempPaint);
    }


上面的是radius =200f,下面的radius = 20f

DiscretePathEffect(float segmentLength, float deviation) -离散路径(小段长,偏移量)

float segmentLength:用来拼接每个线段的长度,float deviation:偏离量
第一个参数:将原来的路径切成多长的线段,越小,所切成的小线段越多
第二参数:被切成的每个小线段的可偏移距离。越大,每个线段的可偏移距离就越大。


    /**
     * 离散路径
     *
     * @param canvas
     */
    private fun discreteEffect(canvas: Canvas) {
        canvas.save();//保存画布状态
        canvas.translate(0F, 100F);
        val path = Path();
        // 定义路径的起点
        path.moveTo(100F, 0F);
        path.lineTo(600F, 100F);
        path.lineTo(1000F, 0F);
        //第一个参数:将原来的路径切成多长的线段,越小,所切成的小线段越多
        //第二参数:被切成的每个小线段的可偏移距离。越大,每个线段的可偏移距离就越大。
        mEffectPaint.setPathEffect(DiscretePathEffect (2F, 5F));
        mEffectPaint.setStrokeWidth(2F);
        canvas.drawPath(path, mEffectPaint);
        canvas.translate(0F, 100F);
        mEffectPaint.setPathEffect( DiscretePathEffect (2F, 20F));
        canvas.drawPath(path, mEffectPaint);

        canvas.translate(0F, 100F);
        mEffectPaint.setPathEffect( DiscretePathEffect (20F, 20F));
        canvas.drawPath(path, mEffectPaint);

        canvas.restore();//重新储存画布状态
    }

从上到下分别参数是(2,5),(2,20),(20,20)

DashPathEffect(float[] intervals, float phase)

float[] intervals:指定了虚线的格式,数组中元素必须为偶数(最少是 2 个),按照「画线长度、空白长度、画线长度、空白长度」……的顺序排列,float phase:虚线的偏移量

下面会动的线是不断改变偏移量:mDashOffSet的结果
166ed2ffe9d97982_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0

    /**
     * 虚线测试
     *
     * @param canvas
     */
    private fun dashEffect(canvas: Canvas) {
        val mEffectPaint = Paint(mRedPaint)
        mEffectPaint.style = Paint.Style.STROKE
        mEffectPaint.strokeWidth = 40f

//        显示100,隐藏50,显示50,隐藏50,的循环
        mEffectPaint.pathEffect = DashPathEffect(floatArrayOf(100F, 50F, 50F, 50F), 0F)
        val path = Path()
        path.moveTo(100F, 650F)
        path.lineTo(1000F, 650F)
        canvas.drawPath(path, mEffectPaint)


        //显示100,隐藏50,显示60,隐藏50,的循环,偏移:mDashOffSet
        val mDashOffSet = 50f // 100f的一半
        mEffectPaint.pathEffect = DashPathEffect(floatArrayOf(100F, 50F, 50F, 50F), mDashOffSet)
        val pathOffset50 = Path()
        pathOffset50.moveTo(100F, 750F)
        pathOffset50.lineTo(1000F, 750F)
        canvas.drawPath(pathOffset50, mEffectPaint)

    }

PathDashPathEffect(Path shape, float advance, float phase, PathDashPathEffect.Style style)

Path shape:用来绘制的Path,float advance:两个相邻Path段起点间的间隔,float phase:虚线的偏移量, PathDashPathEffect.Style style:指定拐弯改变的时候 shape 的转换方式:TRANSLATE:位移、ROTATE:旋转、MORPH:变体

//Path shape:表示[路径点样],这里是一个五角星
//float advance:表示两个[路径点样]间的距离
//float phase:路径绘制偏移距离
//Style style:表示在遇到转角时的过渡样式
// ----Style.ROTATE表示通过旋转[路径点样]来过渡转角;
// ----Style.MORPH表示通过变形[路径点样]来过渡转角;
// ----Style.TRANSLATE表示通过位移[路径点样]来过渡转角。

//一个正四方形,为了更好的看到pathdash变形的样子
public static Path commonPath() {
        Path path = new Path();
        path.moveTo(50, 50);
        path.lineTo(100,50);
        path.lineTo(100,100);
        path.lineTo(50,100);
        path.lineTo(50,50);//最后这个不用写path也会自动闭合
        path.close();
        return path;
    }
     /**
     * 路径点样路径样式
     *
     * @param canvas
     */
    private fun PathDashEffect(canvas: Canvas) {
        canvas.save();
        val path = Path();
        // 定义路径的起点
        path.moveTo(100F, 100F);
        path.lineTo(300F, 400F);
        path.lineTo(1000F, 300F);
        //变形过渡

//        val shape = CommonPath.nStarPath(5, 16F, 4F)
        val shape = CommonPath.commonPath()

        val mDashOffSet = 0F
        //Path shape:表示[路径点样],这里是一个五角星
        //float advance:表示两个[路径点样]间的距离
        //float phase:路径绘制偏移距离
        //Style style:表示在遇到转角时的过渡样式
        // ----Style.ROTATE表示通过旋转[路径点样]来过渡转角;
        // ----Style.MORPH表示通过变形[路径点样]来过渡转角;
        // ----Style.TRANSLATE表示通过位移[路径点样]来过渡转角。
        mEffectPaint.pathEffect = PathDashPathEffect(
            shape, 60F, mDashOffSet, PathDashPathEffect.Style.ROTATE
        )
        canvas.drawPath(path, mEffectPaint);
        canvas.restore();
        //旋转过渡
        canvas.save();
        canvas.translate(0F, 200F);
        mEffectPaint.setPathEffect(
            PathDashPathEffect(
                shape, 60F, mDashOffSet, PathDashPathEffect.Style.MORPH
            )
        );
        canvas.drawPath(path, mEffectPaint);
        canvas.restore();

        //移动过渡
        canvas.save();
        canvas.translate(0F, 500F);
        mEffectPaint.setPathEffect(
            PathDashPathEffect(
                shape, 60F, mDashOffSet, PathDashPathEffect.Style.TRANSLATE
            )
        );
        canvas.drawPath(path, mEffectPaint);
        canvas.restore();
    }

从上到下分别是

  1. Style.ROTATE表示通过旋转[路径点样]来过渡转角,随着路径的旋转而旋转
  2. Style.MORPH表示通过变形[路径点样]来过渡转角,MORPH和ROTATE基本一样,会随着路径的旋转而旋转,但是在拐点的地方,会增加平滑度。
  3. Style.TRANSLATE表示通过位移[路径点样]来过渡转角,不会随着路径的旋转而旋转

注意看第一个的第一个正方形,实际上他旋转了,如果是Style.TRANSLATE正方形就是正方形,因为是Style.ROTATE 所以这个正方形旋转对向了拐角

SumPathEffect(PathEffect first, PathEffect second)

PathEffect first:同时应用的PathEffect,PathEffect second:同时应用的PathEffect
ComposePathEffect是将两个子类进行组合,让你看到的是组合后的样子,而且SumPathEffect则是将这两个子类全的效果全显示出来,然后叠加在一起

/**
     * 叠加样式
     *
     * @param canvas
     */
    private fun sumEffect(canvas: Canvas) {
        mBluePaint.setStyle(Paint.Style.STROKE)
        mBluePaint.setStrokeWidth(4F)

        canvas.save()
        canvas.translate(0F, 100F)
        val path = Path()
        // 定义路径的起点
        path.moveTo(100F, 80F);
        path.lineTo(600F, 300F);
        path.lineTo(1000F, 80F);

        val dPathEffect = DashPathEffect(floatArrayOf(10F, 10F), 0F)
        val cPathEffect = CornerPathEffect(100F)
        mBluePaint.pathEffect = SumPathEffect(dPathEffect, cPathEffect)
        canvas.drawPath(path, mBluePaint)

        canvas.restore()

    }

ComposePathEffect(PathEffect outerpe, PathEffect innerpe)

PathEffect outerpe:后应用的PathEffect,PathEffect innerpe:先应用的PathEffect

  /**
     * 叠加样式
     *
     * @param canvas
     */
    private fun composeEffect(canvas: Canvas) {
        mBluePaint.setStyle(Paint.Style.STROKE)
        mBluePaint.setStrokeWidth(4F)

        canvas.save()
        canvas.translate(0F, 100F)
        val path = Path()
        val mDashOffSet = 0f
        // 定义路径的起点
        path.moveTo(100F, 80F);
        path.lineTo(600F, 300F);
        path.lineTo(1000F, 80F);

        val shape = CommonPath.commonPath()
        val effect1 = PathDashPathEffect(shape, 40F, mDashOffSet, PathDashPathEffect.Style.ROTATE)
        val effect2 = DiscretePathEffect(20F, 20F);

        mBluePaint.setPathEffect(ComposePathEffect(effect1, effect2))//离散效果+样点效果
        mGreenPaint.setPathEffect(effect2)

        canvas.drawPath(path, mBluePaint)
        canvas.drawPath(path, mGreenPaint)

        canvas.restore()

    }

就是在两个effect的基础上叠加,绿色的线是辅助用,可以看到只用了DiscretePathEffect离散效果,
蓝色的的是使用了ComposePathEffect,在离散的线上使用了DiscretePathEffect

setShadowLayer(float radius, float dx, float dy, int shadowColor)

设置阴影图层,处于目标下层图层。float radius:阴影半径,float dx:阴影偏移量,float dy:阴影偏移量,int shadowColor:阴影颜色.注:在硬件加速开启的情况下, setShadowLayer() 只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。如果 shadowColor 是半透明的,阴影的透明度就使用 shadowColor 自己的透明度;而如果 shadowColor 是不透明的,阴影的透明度就使用 paint 的透明度。

setMaskFilter(MaskFilter maskfilter)

设置图层遮罩层,处于目标上层图层。MaskFilter有两个子类:

  1. BlurMaskFilter:模糊效果
  2. EmbossMaskFilter:浮雕效果

注:在硬件加速开启的情况下, setMaskFilter(MaskFilter maskfilter)只支持文字的绘制,文字之外的绘制必须关闭硬件加速才能正常绘制阴影。关闭硬件加速可以调用View.setLayerType(View.LAYER_TYPE_SOFTWARE, null)或者在Activity标签里设置android:hardwareAccelerated="false"

BlurMaskFilter设置画笔模糊阴影效果

mPaint.setMaskFilter(new BlurMaskFilter(20f, BlurMaskFilter.Blur.SOLID));
复制代码
参数1:模糊延伸半径,必须>0;
参数2:有四种枚举
NORMAL,同时绘制图形本身内容+内阴影+外阴影,正常阴影效果
INNER,绘制图形内容本身+内阴影,不绘制外阴影
OUTER,不绘制图形内容以及内阴影,只绘制外阴影
SOLID,只绘制外阴影和图形内容本身,不绘制内阴影
BlurMaskFilter绘制的Bitmap基本完全不受影响

下节看点:Canvas:drawLine/drawPoint

Canvas实现了Android 2D图形的绘制,底层基于Skia实现,Canvas提供了丰富的对象绘制方法,一般都以drawXXX()打头,绘制的对象包括:
弧线(Arcs)
颜色(Argb、Color)
位图(Bitmap)
圆(Circle)
点(Point)
线(Line)
矩形(Rect)
图片(Picture)
圆角矩形(RoundRect)
文本(Text)
顶点(Vertices)
路径(Path)

posted on   7m  阅读(63)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示