Jetpack Compose 绘制 Canvas,DrawScope, 以及Modifier.drawWithContent,BlendMode讲解

这篇文章我们会介绍Compose中如何去自定义绘制一些图形,会介绍Canvas,DrawScope,以及Modifier.drawWithContent的用法。

一 :Canvas,DrawScope

先来看看代码:

@Composable
fun Canvas(modifier: Modifier, onDraw: DrawScope.() -> Unit) =
    Spacer(modifier.drawBehind(onDraw))
复制代码
  • modifier 修饰符以前的文章讲过Modifier用法详解
  • onDraw 其实就是DrawScope。DrawScope点进去查看代码,主要有如下几个方法
    • drawLine 画线
    • drawRect 画矩形
    • drawRoundRect 画圆角矩形
    • drawImage 绘制图片
    • drawCircle 画圆形
    • drawOval 画椭圆形
    • drawArc 画弧度跟扇形
    • drawPath 画路径
    • drawPoints 画点

    还有如下几个扩展方法

    • inset 将DrawScope坐标空间平移
    • translate 平移坐标
    • rotate(旋转坐标)讲的是旋转了多少角度,rotateRad(旋转坐标)讲的是旋转了多少弧度
    • scale 缩放坐标
    • clipRect 裁剪矩形区域,绘制在裁剪好的矩形区域内。ClipOp.Difference从当前剪辑中减去提供的矩形。
    • clipPath 裁剪路径
    • drawIntoCanvas 直接提供底层画布
    • withTransform 组合转换

我们看到会有很多的绘制的方法,一个个来看,一个个来试

1.1 DrawScope的drawLine 方法

fun drawLine(
        brush: Brush,
        start: Offset,
        end: Offset,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = Stroke.DefaultCap,
        pathEffect: PathEffect? = null,
        /*FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

fun drawLine(
        color: Color,
        start: Offset,
        end: Offset,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = Stroke.DefaultCap,
        pathEffect: PathEffect? = null,
        /*FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • brush 是刷子,就填充线的颜色的刷子,主要是处理渐变的。有几种实现如下:\
    1. 线性的渐变
    @Stable
    fun linearGradient(
        vararg colorStops: Pair<Float, Color>,
        start: Offset = Offset.Zero,
        end: Offset = Offset.Infinite,
        tileMode: TileMode = TileMode.Clamp
    ){...}
    
    @Stable
    fun linearGradient(
        colors: List<Color>,
        start: Offset = Offset.Zero,
        end: Offset = Offset.Infinite,
        tileMode: TileMode = TileMode.Clamp
    ){...}
    复制代码
    • colorStops跟colors 都是渐变颜色
    • start 开始的位置
    • end 结束的位置
    • tileMode 有三种 TileMode.Clamp(默认就是这种,最接近该区域的点的颜色),TileMode.Repeated(边从第一种颜色重复到最后一种颜色),TileMode.Mirror (边从最后一种颜色镜像到第一种颜色)
    1. 水平方向的渐变
    @Stable
    fun horizontalGradient(
       colors: List<Color>,
       startX: Float = 0.0f,
       endX: Float = Float.POSITIVE_INFINITY,
       tileMode: TileMode = TileMode.Clamp
    ){...}
    
    @Stable
    fun horizontalGradient(
      vararg colorStops: Pair<Float, Color>,
      startX: Float = 0.0f,
      endX: Float = Float.POSITIVE_INFINITY,
      tileMode: TileMode = TileMode.Clamp
    ){...}
    复制代码
    • colors,colorStops都是渐变的颜色
    • startX 开始的x轴坐标
    • endX 结束的x轴坐标
    • tileMode 跟上面一样
    1. 竖直方向的渐变
    @Stable
    fun verticalGradient(
       colors: List<Color>,
       startY: Float = 0.0f,
       endY: Float = Float.POSITIVE_INFINITY,
       tileMode: TileMode = TileMode.Clamp
    )
    
    @Stable
    fun verticalGradient(
        vararg colorStops: Pair<Float, Color>,
        startY: Float = 0f,
        endY: Float = Float.POSITIVE_INFINITY,
        tileMode: TileMode = TileMode.Clamp
    )
    复制代码
    • colors,colorStops都是渐变的颜色
    • startY 开始的y坐标
    • endY 结束的y坐标
    • tileMode 跟上面一致

    4.径向的渐变

    @Stable
    fun radialGradient(
        vararg colorStops: Pair<Float, Color>,
        center: Offset = Offset.Unspecified,
        radius: Float = Float.POSITIVE_INFINITY,
        tileMode: TileMode = TileMode.Clamp
    ){...}
    
    @Stable
    fun radialGradient(
        colors: List<Color>,
        center: Offset = Offset.Unspecified,
        radius: Float = Float.POSITIVE_INFINITY,
        tileMode: TileMode = TileMode.Clamp
    ){...}
    复制代码
    • colorStops,colors 都是渐变的颜色
    • center 圆形的坐标
    • radius 半径
    • tileMode 跟上面一致
    1. 扫描的渐变
    @Stable
    fun sweepGradient(
       vararg colorStops: Pair<Float, Color>,
       center: Offset = Offset.Unspecified
    )
    
    @Stable
    fun sweepGradient(
       colors: List<Color>,
       center: Offset = Offset.Unspecified
    )
    复制代码
    • colorStops 跟colors 都是渐变的颜色
    • center 扫描的中心点
  • color 是线的颜色
  • start 是线的起始点位置
  • end 是线的终点位置
  • strokeWidth 线的宽度
  • cap 线段末端的形状。 三种取值
    • StrokeCap.Butt平的效果(以平边开始和结束轮廓,没有延伸。)
    • StrokeCap.Round 圆形效果(以半圆延伸开始和结束轮廓)
    • StrokeCap.Square 平的效果(以半正方形延伸开始和结束轮廓)
  • pathEffect 设置显示效果(比如虚线)。实现有如下几种
    • PathEffect.cornerPathEffect(radius: Float) 将线段之间的锐角替换为指定半径的圆角 radius是半径
    • PathEffect.dashPathEffect(intervals: FloatArray, phase: Float = 0f) 将形状绘制为具有给定间隔的一系列破折号。比如虚线 例如interval={20,5},第一个参数表示虚线的长度是20,5是虚线之间的间隔是5. phase 偏移
    • PathEffect.chainPathEffect(outer: PathEffect, inner: PathEffect) 创建一个PathEffect,将内部效果应用于路径,然后应用外部效果
    • PathEffect.stampedPathEffect(shape: Path, advance: Float, phase: Float,style: StampedPathEffectStyle) 用path表示的指定形状冲压绘制的路径. shape要踩踏的路径,advance 每个冲压形状之间的前进间距, phase 在压印第一个形状之前要偏移的相位量, style如何在每个位置转换形状,因为它是冲压. style有三种取值 StampedPathEffectStyle.Translate 平移 ,StampedPathEffectStyle.Rotate 旋转,StampedPathEffectStyle.Morph 变形
  • alpha 透明度
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式。这个后面单独讲

举例划线的例子如下:

@Preview
@Composable
fun customDrawView(){
    Canvas(modifier = Modifier.fillMaxSize(),onDraw = {
        val w = size.width
        val h = size.height
        // 蓝色的线
        drawLine(
            start = Offset(100f,100f),
            end = Offset(w-100f,100f),
            color = Color.Blue,
            strokeWidth = 10f,
            cap = StrokeCap.Square,
            alpha = 1f
        )

        // 渐变的线
        drawLine(
            brush = Brush.linearGradient(
                0.0f to Color.Red,
                0.3f to Color.Green,
                1.0f to Color.Blue,
                start = Offset(100f, 150f),
                end = Offset(w-100f, 150f),
                tileMode = TileMode.Repeated),
            start = Offset(100f,150f),
            end = Offset(w-100f,150f),
            strokeWidth = 10f,
            cap = StrokeCap.Round,
            alpha = 1f
        )

        // 横向渐变的线
        drawLine(
            brush = Brush.horizontalGradient(
                0.0f to Color.Red,
                0.3f to Color.Green,
                1.0f to Color.Blue,
                startX = 100f,
                endX = w-100f,
                tileMode = TileMode.Mirror),
            start = Offset(100f,200f),
            end = Offset(w-100f,200f),
            strokeWidth = 10f,
            cap = StrokeCap.Butt,
            alpha = 1f
        )

        // 竖直渐变的线
        drawLine(
            brush = Brush.verticalGradient(
                0.0f to Color.Red,
                0.3f to Color.Green,
                1.0f to Color.Blue,
                startY = 250f,
                endY = 350f,
                tileMode = TileMode.Clamp),
            start = Offset(100f,250f),
            end = Offset(100f,350f),
            strokeWidth = 10f,
            cap = StrokeCap.Round,
            alpha = 1f
        )

        // 横向渐变的有 pathEffect.dashPathEffect的虚线
        drawLine(
            brush = Brush.horizontalGradient(
                0.0f to Color.Red,
                0.5f to Color.Yellow,
                1.0f to Color.Blue,
                startX = 100f,
                endX = w-100f,
                tileMode = TileMode.Clamp),
            start = Offset(100f,450f),
            end = Offset(w-100f,450f),
            strokeWidth = 10f,
            cap = StrokeCap.Butt,
            alpha = 1f,
            pathEffect = PathEffect.dashPathEffect(floatArrayOf(100f,20f),10f)
        )
    })
}
复制代码

效果如下:
Screenshot_drawlinetest.jpg

1.2 drawRect 和 drawRoundRect 绘制矩形跟圆角矩形

DrawScope里drawRect跟drawRoundRect的代码如下:

fun drawRect(
    brush: Brush,
    topLeft: Offset = Offset.Zero,
    size: Size = this.size.offsetSize(topLeft),
    alpha: Float = 1.0f,
    style: DrawStyle = Fill,
    colorFilter: ColorFilter? = null,
    blendMode: BlendMode = DefaultBlendMode
)
fun drawRect(
    color: Color,
    topLeft: Offset = Offset.Zero,
    size: Size = this.size.offsetSize(topLeft),
    alpha: Float = 1.0f,
    style: DrawStyle = Fill,
    colorFilter: ColorFilter? = null,
    blendMode: BlendMode = DefaultBlendMode
)

fun drawRoundRect(
        brush: Brush,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        cornerRadius: CornerRadius = CornerRadius.Zero,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

fun drawRoundRect(
        color: Color,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        cornerRadius: CornerRadius = CornerRadius.Zero,
        style: DrawStyle = Fill,
        /*@FloatRange(from = 0.0, to = 1.0)*/
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • brush 刷子跟上面讲解drawline的一样
  • color 颜色
  • topLeft 左上角点的坐标
  • size 大小,比如Size(w,h) w是宽度,h是高度
  • cornerRadius 是具体的圆角
  • alpha 透明度
  • style DrawStyle 可以是Fill填充,也可以是Stroke,线条
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式 后面讲

举例:

@Preview
@Composable
fun drawRectTest(){
    Canvas(modifier = Modifier.fillMaxSize()) {
        val w= size.width
        val h = size.height
        // 蓝底的矩形
        drawRect(
            color = Color.Blue,
            topLeft = Offset(100f,100f),
            size = Size(100f,100f),
            alpha = 1f,
            style = Fill
        )

        // 渐变的矩形
        drawRect(
            brush = Brush.linearGradient(
                0.0f to Color.Red,
                0.3f to Color.Green,
                1.0f to Color.Blue,
                start = Offset(300f, 100f),
                end = Offset(300f, 200f),
                tileMode = TileMode.Repeated),
            topLeft = Offset(300f,100f),
            size = Size(100f,100f),
            alpha = 1f,
            style = Fill
        )

        // 线框的矩形
        drawRect(
            color = Color.Blue,
            topLeft = Offset(100f,300f),
            size = Size(100f,100f),
            alpha = 1f,
            style = Stroke(width=1f,cap = StrokeCap.Butt)
        )

        // 线框的矩形
        drawRoundRect(
            color = Color.Blue,
            topLeft = Offset(300f,300f),
            size = Size(300f,100f),
            alpha = 1f,
            style = Fill,
            cornerRadius = CornerRadius(10f,10f)
        )

        // 渐变的矩形
        drawRoundRect(
            brush = Brush.linearGradient(
                0.0f to Color.Red,
                0.3f to Color.Green,
                1.0f to Color.Blue,
                start = Offset(300f, 450f),
                end = Offset(600f, 550f),
                tileMode = TileMode.Repeated),
            topLeft = Offset(300f,450f),
            size = Size(300f,100f),
            alpha = 1f,
            style = Fill,
            cornerRadius = CornerRadius(10f,10f)
        )
    }
}
复制代码

效果图:
Screenshot_drawrectandroundrect.jpg

1.3 drawImage 绘制图片

DrawScope里绘制图片的代码如下:

fun drawImage(
  image: ImageBitmap,
  topLeft: Offset = Offset.Zero,
  alpha: Float = 1.0f,
  style: DrawStyle = Fill,
  colorFilter: ColorFilter? = null,
  blendMode: BlendMode = DefaultBlendMode
)

fun drawImage(
    image: ImageBitmap,
    srcOffset: IntOffset = IntOffset.Zero,
    srcSize: IntSize = IntSize(image.width, image.height),
    dstOffset: IntOffset = IntOffset.Zero,
    dstSize: IntSize = srcSize,
    alpha: Float = 1.0f,
    style: DrawStyle = Fill,
    colorFilter: ColorFilter? = null,
    blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • image 图片
  • topLeft 左上角
  • alpha 透明度
  • style 绘制类型是Fill还是Stoke
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式后面讲
  • srcOffset IntOffset类型原图像的偏移
  • srcSize IntSize类型 原图像的大小
  • dstOffset IntOffset 类型目标图像的左上角的位置
  • dstSize IntSize大小 目标图像的大小

举例:

@Preview
@Composable
fun drawImageTest(){
    val imageBitmap = ImageBitmap.imageResource(id = R.drawable.icon_head)
    Canvas(modifier = Modifier.fillMaxSize()) {
        val w= size.width
        val h = size.height

        drawImage(
            image = imageBitmap,
            topLeft = Offset(50f,50f),
            alpha = 1f,
            style = Fill
        )

        drawImage(
            image = imageBitmap,
            srcOffset = IntOffset(0,0),
            srcSize = IntSize(imageBitmap.width,imageBitmap.height),
            dstOffset = IntOffset(50,imageBitmap.height+100),
            dstSize = IntSize(200,200),
            blendMode = BlendMode.SrcOver
        )
    }
}
复制代码

效果图:

Screenshot_drawimage.jpg

1.4 drawCircle绘制圆形

DrawScope里drawCircle的代码如下:

fun drawCircle(
        brush: Brush,
        radius: Float = size.minDimension / 2.0f,
        center: Offset = this.center,
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

fun drawCircle(
        color: Color,
        radius: Float = size.minDimension / 2.0f,
        center: Offset = this.center,
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • brush 刷子跟上面一致
  • color 颜色
  • radius 半径
  • center 圆形
  • alpha 透明度
  • style 填充方式 Fill跟Stroke
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式 后面讲

举例:

@Preview
@Composable
fun drawCircleTest(){
    Canvas(modifier = Modifier.fillMaxSize()) {
        val w= size.width
        val h = size.height

        drawCircle(
            color = Color.Red,
            radius = 100f,
            center = Offset(150f,150f),
            alpha = 1f,
            style = Fill
        )

        drawCircle(
            color = Color.Red,
            radius = 100f,
            center = Offset(400f,150f),
            alpha = 1f,
            style = Stroke(width = 5f)
        )

        drawCircle(
            brush = Brush.radialGradient(
                0.0f to Color.Red,
                0.5f to Color.Green,
                1.0f to Color.Blue,
                center = Offset(150f,360f),
                radius = 100f,
                tileMode = TileMode.Clamp
            ),
            radius = 100f,
            center = Offset(150f,360f),
            alpha = 1f,
            style = Fill
        )
    }
}
复制代码

效果图:
Screenshot_drawcircle.jpg

1.5 drawOval绘制椭圆形

DrawScope中drawOval的代码:

fun drawOval(
        brush: Brush,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

fun drawOval(
        color: Color,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • brush 刷子跟上面一致
  • color 颜色
  • topLeft 左上角的坐标
  • size 大小
  • alpha 透明度
  • style 填充类型
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式 后面讲

举例:

@Preview
@Composable
fun drawOvalTest(){
    Canvas(modifier = Modifier.fillMaxSize()) {
        val w= size.width
        val h = size.height

        drawOval(
            color = Color.Red,
            topLeft = Offset(50f,50f),
            size = Size(200f,100f),
            alpha = 1f,
            style = Stroke(width = 5f)
        )

        drawOval(
            color = Color.Red,
            topLeft = Offset(300f,50f),
            size = Size(200f,100f),
            alpha = 1f,
            style = Fill
        )

        drawOval(
            brush = Brush.horizontalGradient(
                0.0f to Color.Red,
                0.5f to Color.Green,
                1.0f to Color.Blue,
                startX = 50f,
                endX = 250f,
            ),
            topLeft = Offset(50f,180f),
            size = Size(200f,100f),
            alpha = 1f,
            style = Fill
        )
    }
}
复制代码

效果图:
Screenshot_drawoval.jpg

1.6 drawArc 绘制弧跟扇形

DrawScope中drawArc代码如下:

fun drawArc(
        brush: Brush,
        startAngle: Float,
        sweepAngle: Float,
        useCenter: Boolean,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

fun drawArc(
        color: Color,
        startAngle: Float,
        sweepAngle: Float,
        useCenter: Boolean,
        topLeft: Offset = Offset.Zero,
        size: Size = this.size.offsetSize(topLeft),
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • brush 刷子
  • color 颜色
  • startAngle 开始的角度
  • sweepAngle 划过的角度
  • useCenter 是否连接圆心
  • topLeft 左上角的位置
  • size 大小
  • alpha 透明度
  • style 填充模式
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式 后面讲

举例:

@Preview
@Composable
fun drawArcTest(){
        Canvas(modifier = Modifier.fillMaxSize()) {
        val w= size.width
        val h = size.height

        drawArc(
            color = Color.Red,
            startAngle = 90f,
            sweepAngle = 100f,
            useCenter = false,
            alpha = 1f,
            style = Fill,
            topLeft = Offset(50f,50f),
            size = Size(200f,200f)
        )

        drawArc(
            color = Color.Red,
            startAngle = 0f,
            sweepAngle = 100f,
            useCenter = true,
            alpha = 1f,
            style = Fill,
            topLeft = Offset(150f,50f),
            size = Size(200f,200f)
        )

        drawArc(
            color = Color.Red,
            startAngle = 0f,
            sweepAngle = 100f,
            useCenter = false,
            alpha = 1f,
            style = Stroke(width = 5f),
            topLeft = Offset(300f,50f),
            size = Size(200f,200f)
        )

        drawArc(
            color = Color.Red,
            startAngle = 0f,
            sweepAngle = 100f,
            useCenter = true,
            alpha = 1f,
            style = Stroke(width = 5f),
            topLeft = Offset(500f,50f),
            size = Size(200f,200f)
        )

        drawArc(
            brush = Brush.horizontalGradient(
                0.0f to Color.Red,
                0.5f to Color.Green,
                1.0f to Color.Blue,
                startX = 50f,
                endX = 250f,
            ),
            startAngle = 0f,
            sweepAngle = 100f,
            useCenter = true,
            alpha = 1f,
            style = Stroke(width = 5f),
            topLeft = Offset(50f,350f),
            size = Size(200f,200f)
        )

        drawArc(
            brush = Brush.horizontalGradient(
                    0.0f to Color.Red,
                    0.5f to Color.Green,
                    1.0f to Color.Blue,
                    startX = 200f,
                    endX = 450f,
            ),
            startAngle = 0f,
            sweepAngle = 100f,
            useCenter = true,
            alpha = 1f,
            style = Fill,
            topLeft = Offset(200f,350f),
            size = Size(200f,200f)
        )
    }
}
复制代码

效果图如下:
Screenshot_drawarc.jpg

1.7 drawPath 绘制路径

fun drawPath(
        path: Path,
        color: Color,
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

fun drawPath(
        path: Path,
        brush: Brush,
        alpha: Float = 1.0f,
        style: DrawStyle = Fill,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • path 路径
  • brush 刷子
  • color 颜色
  • alpha 透明度
  • style 填充类型
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式

举例:

@Preview
@Composable
fun drawPathTest(){
    Canvas(modifier = Modifier.fillMaxSize()) {
        val path = Path()
        path.moveTo(50f,50f)
        path.lineTo(50f,150f)
        path.lineTo(200f,50f)
        path.close()
        drawPath(
            path = path,
            color = Color.Red
        )

        path.moveTo(50f,200f)
        path.lineTo(50f,350f)
        path.lineTo(200f,200f)
        path.close()
        drawPath(
            path = path,
            color = Color.Red,
            style = Stroke(width = 4f)
        )
    }
}
复制代码

效果图如下:

Screenshot_drawpath.jpg

1.8 drawPoints 绘制点

fun drawPoints(
        points: List<Offset>,
        pointMode: PointMode,
        color: Color,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = StrokeCap.Butt,
        pathEffect: PathEffect? = null,
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)

fun drawPoints(
        points: List<Offset>,
        pointMode: PointMode,
        brush: Brush,
        strokeWidth: Float = Stroke.HairlineWidth,
        cap: StrokeCap = StrokeCap.Butt,
        pathEffect: PathEffect? = null,
        alpha: Float = 1.0f,
        colorFilter: ColorFilter? = null,
        blendMode: BlendMode = DefaultBlendMode
)
复制代码
  • points 点的集合
  • pointMode 用于指示如何绘制点。有三种取值PointMode.Points 分别画点,PointMode.Lines 画线(点集合两两组合划线,奇数的话最后一个不管),PointMode.Polygon 画多边形
  • color 颜色
  • brush 刷子 设置渐变
  • strokeWidth 宽度
  • pathEffect 跟上面drawLine的一致
  • alpha 透明度
  • colorFilter 用于修改在其安装的[Paint]上绘制的每个像素的颜色的效果。Image讲解里有讲到
  • blendMode 混合模式

举例:

@Preview
@Composable
fun drawPointsTest(){
    Canvas(modifier = Modifier.fillMaxSize()) {
        val points = ArrayList<Offset>().apply {
            add(Offset(50f,50f))
            add(Offset(100f,50f))
            add(Offset(100f,100f))
            add(Offset(80f,100f))
        }

        val points2 = ArrayList<Offset>().apply {
            add(Offset(50f,250f))
            add(Offset(100f,250f))
            add(Offset(100f,300f))
            add(Offset(80f,300f))
            add(Offset(300f,300f))
        }

        val points3 = ArrayList<Offset>().apply {
            add(Offset(250f,250f))
            add(Offset(300f,250f))
            add(Offset(300f,300f))
            add(Offset(280f,300f))
            add(Offset(250f,250f))
        }

        drawPoints(
            points = points,
            pointMode = PointMode.Points,
            color = Color.Blue,
            strokeWidth = 15f,
            alpha = 1f
        )

        drawPoints(
            points = points2,
            pointMode = PointMode.Lines,
            color = Color.Blue,
            strokeWidth = 5f,
            alpha = 1f
        )

        drawPoints(
            points = points3,
            pointMode = PointMode.Polygon,
            color = Color.Blue,
            strokeWidth = 5f,
            cap = StrokeCap.Round,
            alpha = 1f
        )
    }
}
复制代码

效果如下:

drawpoints.png

1.9 inset 平移坐标

inset是将DrawScope坐标空间平移,inset的代码如下:

inline fun DrawScope.inset(
    left: Float,
    top: Float,
    right: Float,
    bottom: Float,
    block: DrawScope.() -> Unit
){...}

inline fun DrawScope.inset(
    horizontal: Float = 0.0f,
    vertical: Float = 0.0f,
    block: DrawScope.() -> Unit
){...}

inline fun DrawScope.inset(
    inset: Float,
    block: DrawScope.() -> Unit
){...}
复制代码
  • left左边平移多少
  • top 顶部平移多少
  • right 右边平移多少
  • bottom 底部平移多少
  • horizontal 横向平移多少
  • vertical 竖向平移多少
  • inset

我们通过一个例子:绘制一个矩形,宽高是分别是屏幕宽高的一半,同时绘制一个inset了屏幕一半宽高的矩形。代码如下:

@Preview
@Composable
fun drawInsetTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasQuadrantSize = size / 2F
        drawRect(
            color = Color.Green,
            size = canvasQuadrantSize
        )

        inset(canvasQuadrantSize.width,canvasQuadrantSize.height){
            drawRect(
                color = Color.Red,
                size = canvasQuadrantSize
            )
        }
    }
}
复制代码

效果如图:左上角的是正常绘制的矩形,右下角的是通过inset平移了的
Screenshot_drawinset.jpg

2.0 translate 平移坐标

translate平移坐标,代码如下:

inline fun DrawScope.translate(
    left: Float = 0.0f,
    top: Float = 0.0f,
    block: DrawScope.() -> Unit
){...}
复制代码
  • left 左边平移多少
  • top顶部平移多少

举例:

@Preview
@Composable
fun drawTranslateTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasQuadrantSize = size / 2F
        translate(canvasQuadrantSize.width,0f){
            drawRect(
                color = Color.Blue,
                size = canvasQuadrantSize
            )
        }
    }
}
复制代码

效果如下:
Screenshot_translate.jpg

2.1 rotate,rotateRad 旋转坐标

rotate,和rotateRad都是旋转,但是rotate的是旋转了多少角度,rotateRad是旋转了多少弧度。,代码如下:

inline fun DrawScope.rotate(
    degrees: Float,
    pivot: Offset = center,
    block: DrawScope.() -> Unit
){...}

inline fun DrawScope.rotateRad(
    radians: Float,
    pivot: Offset = center,
    block: DrawScope.() -> Unit
){...}
复制代码
  • degrees 顺时针旋转的角度
  • pivot 旋转的中心点 (默认是图形的中心)
  • radians 顺时针旋转的弧度
  • pivot 旋转的中心点(默认是图形的中心)

举例:

@Preview
@Composable
fun drawRotateTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasSize = size
        val canvasWidth = size.width
        val canvasHeight = size.height
        rotate(degrees = 45F){
            drawRect(
                color = Color.Gray,
                topLeft = Offset(x = canvasWidth / 3F , y = canvasHeight / 3F),
                size = canvasSize / 3F
            )
        }
    }
}

@Preview
@Composable
fun drawRotateRabTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasSize = size
        val canvasWidth = size.width
        val canvasHeight = size.height
        rotateRad(45F){
            drawRect(
                color = Color.Red,
                topLeft = Offset(x = canvasWidth / 3F , y = canvasHeight / 3F),
                size = canvasSize / 3F
            )
        }
    }
}
复制代码

drawRotateTest的效果如下,drawRotateRabTest的可以自行运行代码查看效果
Screenshot_rotate.jpg

2.2 scale 缩放坐标

scale 是缩放坐标,代码如下:

inline fun DrawScope.scale(
    scale: Float,
    pivot: Offset = center,
    block: DrawScope.() -> Unit
){...}

inline fun DrawScope.scale(
    scaleX: Float,
    scaleY: Float,
    pivot: Offset = center,
    block: DrawScope.() -> Unit
){...}
复制代码
  • scale 缩放的大小
  • pivot 缩放的中心点
  • scaleX X轴缩放多少
  • scaleY Y轴缩放所少
  • pivot 缩放的中心点

举例:

@Preview
@Composable
fun scaleTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasSize = size
        val canvasWidth = size.width
        val canvasHeight = size.height
        
        scale(scale =0.5f){
            drawRect(
                color = Color.Gray,
                topLeft = Offset(x = canvasWidth / 3F , y = canvasHeight / 3F),
                size = canvasSize / 3F
            )
        }
    }
}
复制代码

效果图如下:
Screenshot_scale.jpg

2.3 clipRect 裁剪矩形区域

clipRect 是裁剪出一块矩形的区域,代码如下:

inline fun DrawScope.clipRect(
    left: Float = 0.0f,
    top: Float = 0.0f,
    right: Float = size.width,
    bottom: Float = size.height,
    clipOp: ClipOp = ClipOp.Intersect,
    block: DrawScope.() -> Unit
)
复制代码
  • left 左边的坐标
  • top 顶部的坐标
  • right 右边的坐标
  • bottom 底部的坐标
  • clipOp 裁剪的类型ClipOp ClipOp.Intersect是裁剪出来的矩形。ClipOp.Difference从当前剪辑中减去提供的矩形

举例:比如绘制整个屏幕大小的矩形。但是我们设置了裁剪,代码如下:

@Preview
@Composable
fun clipRectTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasSize = size
        val canvasWidth = size.width
        val canvasHeight = size.height

        clipRect(
            left = 100f,
            top = 100f,
            right = canvasWidth-100f,
            bottom = canvasHeight-100f,
            clipOp = ClipOp.Intersect
        ){
            drawRect(
                color = Color.Gray,
                size = canvasSize
            )
        }
    }
}
复制代码

效果如下:

Screenshot_cliprect.jpg

2.4 clipPath 裁剪路径

clipPath 是根据path路径去裁剪出区域。代码如下:

inline fun DrawScope.clipPath(
    path: Path,
    clipOp: ClipOp = ClipOp.Intersect,
    block: DrawScope.() -> Unit
)
复制代码
  • path 是路径
  • clipOp 裁剪的类型ClipOp ClipOp.Intersect是裁剪出来的矩形。ClipOp.Difference从当前剪辑中减去

我们可以把path写成一个矩形,跟上面clipRect例子中达到一样的效果。同举例如下:

@Preview
@Composable
fun clipPathTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasSize = size
        val canvasWidth = size.width
        val canvasHeight = size.height
        val path = Path()
        path.moveTo(100f,100f)
        path.lineTo(canvasWidth-100f,100f)
        path.lineTo(canvasWidth-100f,canvasHeight-100f)
        path.lineTo(100f,canvasHeight-100f)
        path.close()
        clipPath(
            path = path,
            clipOp = ClipOp.Intersect
        ){
            drawRect(
                color = Color.Gray,
                size = canvasSize
            )
        }
    }
}
复制代码

效果跟上面的clipRect的例子一致

2.5 drawIntoCanvas 直接提供了画布

drawIntoCanvas 是直接提供了画布,让我们可以使用画布直接绘制。代码如下:

inline fun DrawScope.drawIntoCanvas(block: (Canvas) -> Unit) = block(drawContext.canvas)
复制代码

block的入参直接是个canvas,方便我们直接使用canvas进行绘制。
举例:比如我们画一条线

@Preview
@Composable
fun drawIntoCanvasTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        drawIntoCanvas {
            val paint = Paint()
            paint.color = Color.Red
            paint.strokeWidth = 10f
            it.drawLine(p1= Offset(50f,50f),p2= Offset(200f,200f),paint = paint)
        }
    }
}
复制代码

效果如下:
Screenshot_line.jpg

2.6 withTransform 组合转换

withTransform是组合转换,比如如果同时想要有平移跟旋转的效果,可以使用withTransform。withTransform的代码如下:

inline fun DrawScope.withTransform(
    transformBlock: DrawTransform.() -> Unit,
    drawBlock: DrawScope.() -> Unit
){...}
复制代码
  • transformBlock就是我们的各种转换

举例:

@Preview
@Composable
fun drawScaleTest(){
    Canvas(modifier = Modifier.fillMaxSize()){
        val canvasSize = size
        val canvasWidth = size.width
        val canvasHeight = size.height

        withTransform({
            translate(left = canvasWidth/5F)
            rotate(degrees=45F)
        }) {
            drawRect(
                color = Color.Gray,
                topLeft = Offset(x = canvasWidth / 3F, y = canvasHeight / 3F),
                size = canvasSize / 3F
            )
        }
    }
}
复制代码

效果如下:
Screenshot_cliprect.jpg

二 :Modifier.drawWithContent

Modifier.drawWithContent的扩展方法也可以进行自定义绘制,我们来看看它的代码:

 fun Modifier.drawWithContent(
    onDraw: ContentDrawScope.() -> Unit
): Modifier = this.then(
    object : DrawModifier, InspectorValueInfo(
        debugInspectorInfo {
            name = "drawWithContent"
            properties["onDraw"] = onDraw
        }
    ) {
        override fun ContentDrawScope.draw() {
            onDraw()
        }
    }
)

interface ContentDrawScope : DrawScope {
    /**
     * Causes child drawing operations to run during the `onPaint` lambda.
     */
    fun drawContent()
}
复制代码

可以看到ContentDrawScope是DrawScope的子类。所以可以使用DrawScope的所有的方法。我们使用drawWithContent举个绘制小红点的例子(这个例子是在扔物线的视频里看到过)。
代码如下:

@Preview
@Composable
fun drawWithContentTest(){
    Column() {
        Image(bitmap = ImageBitmap.imageResource(id = R.drawable.icon_head),
            contentScale = ContentScale.Crop,
            contentDescription = "头像",
            modifier = Modifier
                .padding(8.dp)
                .size(50.dp)
                .redPoint()
                .clip(RoundedCornerShape(4.dp))
        )
    }
}

fun Modifier.redPoint():Modifier=drawWithContent {
    drawContent()
    drawIntoCanvas {
        val paint = Paint().apply {
            color = Color.Red
        }
        it.drawCircle(Offset(size.width-1.dp.toPx(),1.dp.toPx()),5.dp.toPx(),paint = paint)
    }
}
复制代码

我们抽出来的方法Modifier.redPoint()可以使用在任何的地方
效果如下:
Screenshot_redpoint.jpg

三:BlendMode 混合模式的讲解

我们的BlenModel是混合模式,跟我们以前View系统时候的Xfermode的PorterDuff.Model类似。不过现在的种类更多,我们一个个来解释。
混合是有个原图像src,一个dst目标图像,这两个图的相交区域的各种组合情况。

  • Clear 清除 (删除源图像和目标图像,不留下任何内容)
  • Src 删除目标图像,只绘制源图像
  • Dst 删除源图像,只绘制目标图像
  • SrcOver 源图和目标图合成,源图在上
  • DstOver 目标图和源图合成,目标图在上
  • SrcIn 显示源图和目标图相交的部分,并且只显示源图像
  • DstIn 显示目标图和源图相交的部分,并且只显示目标图
  • SrcOut 显示源图像和目标图不相交的部分,并且只显示源图
  • DstOut 显示目标图和源图像不相交的部分,并且只显示目标图
  • SrcAtop 显示目标图,并且在相交的地方显示源图
  • DstAtop 显示源图,并在相交的地方显示目标图
  • Xor 显示源图和目标图,但相交的位置不显示(代码注释:对源图像和目标图像应用按位异或运算符。这就使得它们重叠的地方保持透明。)
  • Plus 对源映像和目标映像的组件求和。其中一个图像的像素中的透明度降低了该图像对相应输出像素的贡献,就好像该图像中该像素的颜色较暗一样
  • Modulate 将源图像和目标图像的颜色分量相乘。这只能产生相同或较深的颜色(乘以白色,1.0,结果不变;乘以黑色(0.0,结果为黑色)。合成两个不透明图像时,这与在投影仪上重叠两个透明胶片的效果类似。对于同样乘以alpha通道的变量,请考虑乘以。
  • Screen 将源图像和目标图像的分量的逆相乘,然后求逆结果。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。这基本上与调制混合模式相同,但是在乘法之前颜色值反转,结果在渲染之前反转回来。这只能产生相同或较浅的颜色(乘以黑色,1.0,结果不变;乘以白色(0.0,结果为白色)。类似地,在alpha通道中,它只能产生更不透明的颜色。这与两台投影仪同时在同一屏幕上显示图像的效果相似。
  • Overlay 将源图像和目标图像的分量相乘,然后调整它们以支持目标。具体来说,如果目标值较小,则将其与源值相乘,而如果源值较小,则将源值的倒数与目标值的倒数相乘,然后反转结果。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。
  • Darken 通过从每个颜色通道中选择最低值来合成源图像和目标图像。输出图像的不透明度的计算方法与SrcOver相同。
  • Lighten 通过从每个颜色通道中选择最高值来合成源图像和目标图像。输出图像的不透明度的计算方法与SrcOver相同。
  • ColorDodge 将目标除以源的倒数。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。注意这个BlendMode只能在androidapi级别29及以上使用
  • ColorBurn :将目标的倒数除以源的倒数,然后求结果的倒数。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。注意这个BlendMode只能在androidapi级别29及以上使用
  • Hardlight : 将源图像和目标图像的分量相乘,然后调整它们以有利于源图像。具体来说,如果源值较小,则将其与目标值相乘,而如果目标值较小,则将目标值的倒数与源值的倒数相乘,然后反转结果。反转组件意味着完全饱和的通道(不透明白色)被视为值0.0,而通常被视为0.0(黑色,透明)的值被视为1.0。注意这个BlendMode只能在androidapi级别29及以上使用
  • Softlight: 对于低于0.5的源值,使用ColorDodge;对于高于0.5的源值,使用ColorBurn。这会产生类似的效果,但比叠加效果更柔和。注意这个BlendMode只能在androidapi级别29及以上使用
  • Difference: 从每个通道的较大值中减去较小的值。合成黑没有效果;合成白色将反转其他图像的颜色。输出图像的不透明度的计算方法与SrcOver相同。注意这个BlendMode只能在androidapi级别29及以上使用这种影响类似于排斥,但更为严厉。
  • Exclusion: 从两个图像的总和中减去两个图像乘积的两倍。合成黑没有效果;合成白色将反转其他图像的颜色。输出图像的不透明度的计算方法与SrcOver相同。注意这个BlendMode只能在androidapi级别29及以上使用效果类似于差异,但更柔和。
  • Multiply: 将源图像和目标图像的分量相乘,包括alpha通道。这只能产生相同或较深的颜色(乘以白色,1.0,结果不变;乘以黑色(0.0,结果为黑色)。由于alpha通道也会相乘,因此一个图像中的完全透明像素(不透明度0.0)会导致输出中的完全透明像素。这与DstIn类似,但颜色组合在一起。
  • Hue: 获取源图像的色调,以及目标图像的饱和度和亮度。其效果是用源图像着色目标图像。输出图像的不透明度的计算方法与SrcOver相同。在源图像中完全透明的区域从目标图像获取其色调。注意这个BlendMode只能在androidapi级别29及以上使用
  • Saturation: 获取源图像的饱和度,以及目标图像的色调和亮度。输出图像的不透明度的计算方法与SrcOver相同。在源图像中完全透明的区域从目标图像获取其饱和度。注意这个BlendMode只能在androidapi级别29及以上使用
  • Color :获取源图像的色调和饱和度,以及目标图像的亮度。其效果是用源图像着色目标图像。输出图像的不透明度的计算方法与SrcOver相同。源图像中完全透明的区域从目标处获取其色调和饱和度。注意这个BlendMode只能在androidapi级别29及以上使用
  • Luminosity :获取源图像的亮度,以及目标图像的色调和饱和度。输出图像的不透明度的计算方法与SrcOver相同。在源图像中完全透明的区域从目标图像获取其亮度。注意这个BlendMode只能在androidapi级别29及以上使用

举例,我们使用一个圆形和正方形的形状混合作为例子查看效果。代码如下:

@Preview
@Composable
fun blenModelTest(){
    Canvas(modifier = Modifier.size(300.dp,300.dp)){

        // 作为dst,目标图
        drawCircle(
            color = Color.Red,
            radius = 100f,
            center = Offset(100f,100f)
        )

        // 作为src 源图
        drawRect(
            color = Color.Blue,
            topLeft = Offset(100f,100f),
            size = Size(200f,200f),
            blendMode = BlendMode.Dst
        )
    }
}
复制代码

Dst 是目标图

blendmode_dst.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享