Jetpack Compose - Canvas

Posted 乐翁龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jetpack Compose - Canvas相关的知识,希望对你有一定的参考价值。

Jetpack Compose - Canvas

Compose系列文章,请点原文阅读。原文,是时候学习Compose了!

0、介绍

Canvas 是允许您在屏幕上指定区域并在此区域上执行绘制的组件。您必须使用修饰符指定尺寸,无论是通过Modifier.size修饰符指定确切尺寸,还是通过Modifier.fillMaxSize,ColumnScope.weight等相对于父级指定精确尺寸。如果父级包装了此子级,则仅必须指定确切尺寸。

1、属性一览

【目前基于beta 01版本】该函数在androidx.compose.foundation包下:

@Composable fun Canvas(
    modifier: Modifier, 
    onDraw: DrawScope.() -> Unit
): Unit

属性参数含义:

参数含义
modifier: Modifier = Modifier应用于布局的强制性修饰符【必须指定尺寸】
onDraw: DrawScope.() -> Unit被调用执行绘制

看起来好像是很简单,但其实重点不在于Canvas函数。而在于它的那些用于具体绘制的函数,这篇文章我们先看下类似View视图中的一些draw函数,在androidx.compose.ui.graphics.drawscope包下:

  • drawArc
  • drawCircle
  • drawImage
  • drawLine
  • drawOval
  • drawPath
  • drawPoints
  • drawRect
  • drawRoundRect

2、使用示例

在以上函数中,除了drawImage函数没有color参数,其他的函数都提供了color和brush的两种方式,关于brush请查看之前的Modifier相关文章,一下文章我们只使用color来进行演示。

具体的函数及参数请查看源码,本文只实现其必选参数完成演示,并不一定会用到所有的参数。

2.1、drawArc

该函数必须实现的参数有color,startAngle(开始的角度,0是水平右侧,负数逆时针,正数顺时针),sweepAngle (扇形的角度,正数顺时针),useCenter为boolen值,具体影响效果根据如下代码查看:

@Composable
fun DrawArcDemo() 
    Row() 
    	//示例1
        Canvas(modifier = Modifier.size(100.dp), onDraw = 
            drawArc(
                color = myRed,
                startAngle = 0f,
                sweepAngle = 270f,
                useCenter = true
            )
        )
		//示例2
        Canvas(modifier = Modifier.size(100.dp), onDraw = 
            drawArc(
                color = myRed,
                startAngle = 0f,
                sweepAngle = 270f,
                useCenter = false
            )
        )
		//示例3
        Canvas(modifier = Modifier.size(100.dp), onDraw = 
            drawArc(
                color = myRed,
                startAngle = -90f,
                sweepAngle = 90f,
                useCenter = false
            )
        )
    

绘制结果如下所示,根据上述代码应该很好理解各个参数的作用了:

示例1示例2示例3

2.2、drawCircle

绘制圆形,实心圆形和空心圆形如下代码所示:

@Composable
fun DrawCircleDemo() 
    Row() 
        Canvas(modifier = Modifier.size(200.dp), onDraw = 
            drawCircle(
                color = myRed,
                radius = 200f,
                style = Fill
            )
        )

        Canvas(modifier = Modifier.size(200.dp), onDraw = 
            drawCircle(
                color = myRed,
                radius = 200f,
                style = Stroke(width = 10f)
            )
        )
    

绘制效果如下所示,需要注意Stroke的边框是会从半径处开始,往外和内同时延伸的:

示例1示例2

2.3、drawImage

绘制图片,刚开始只知道需要一个实现了ImageBitmap接口的类型,然后找了半天没找到是怎么根据图片资源ID加载资源的,最后又看了下接口,发现了companion object,这才找到如下方式(还不确定如何释放资源):

@Composable
fun DrawImageDemo() 

    val imageBitmap = ImageBitmap.imageResource(id = R.drawable.like)

    Canvas(modifier = Modifier.size(200.dp), onDraw = 
        drawImage(
            image = imageBitmap
        )
    )

绘制效果如下所示:

2.4、drawLine

绘制线段,代码如下所示:

@Composable
fun DrawLineDemo() 
     Canvas(modifier = Modifier
         .width(200.dp)
         .height(50.dp), onDraw = 
         drawLine(
             color = myRed,
             start = Offset(0f, 0f),
             end = Offset(400f, 0f),
             strokeWidth = 40f
         )
     )

绘制效果如下图中左侧的线段所示,可以看到其末端都是直角,当我们给drawLine函数添加cap = StrokeCap.Round参数后,效果如下图右侧线段所示:

2.5、drawOval

绘制椭圆:

@Composable
fun DrawOvalDemo() 
     Canvas(modifier = Modifier.size(200.dp), onDraw = 
         drawOval(
             color = myRed,
         )
     )

效果如下示例1所示,添加size参数size = Size(size.width, size.height * 0.5f),如下图示例2所示。再添加topLeft参数topLeft = Offset(0f, size.height / 4),效果如示例3所示:

示例1示例2示例3

2.6、drawPath

根据路径绘制相关图形,需要使用Path,对Path不熟悉的可以先看下原来自定义View中的Path来理解下。如下所示代码中,我们定义了一个路径,从左上角顶点直线连接到画布中心,然后连接到画布的左下角,最后闭合,得到一个三角形的路径:

@Composable
fun DrawPathDemo() 
    Canvas(modifier = Modifier.size(200.dp), onDraw = 

        val myPath = Path()
        myPath.lineTo(size.width / 2, size.height / 2)
        myPath.lineTo(0f, size.height)
        myPath.close()

        drawPath(
            path = myPath,
            color = myRed,
            style = Stroke(width = 20f)
        )
    )

这里我们首先使用的是空心的类型style = Stroke(width = 20f),效果如下示例1所示,当我们取消该参数的时候,效果如下图示例2所示:

示例1示例2

2.7、drawPoints

绘制点,我们为了演示效果,使用strokeWidth 参数设置点的宽度为40f。下面示例代码先根据画布大小声明了两个点,分别位于竖直居中方向的1/3 ,2/3处,如下代码所示:

@Composable
fun DrawPointsDemo() 
    Canvas(modifier = Modifier.size(200.dp), onDraw = 
        val myPoints = arrayListOf(
            Offset(size.width / 3, size.height / 2),
            Offset(size.width / 3 * 2, size.height / 2),
        )

        drawPoints(
            color = myRed,
            points = myPoints,
            pointMode = PointMode.Points,
            strokeWidth = 40f
        )
    )

示例效果如下图示例1所示,然后我们给其设置cap参数为cap = StrokeCap.Round,效果如下图示例2所示,直角点变成了圆点:

示例1示例2

2.8、drawRect,drawRoundRect

绘制矩形和圆角矩形类似,我们这里就使用圆角矩形来进行演示了,如下代码所示:

@Composable
fun DrawReactDemo() 
    Canvas(modifier = Modifier.size(200.dp), onDraw = 
        drawRoundRect(
            color = myRed,
        )
    )

以上代码可以直接绘制一个直角矩形,并且是实心的,绘制效果如下示例1所示。

假如我们需要绘制空心的圆角矩形,代码如下所示:

@Composable
fun DrawReactDemo() 
    Canvas(modifier = Modifier.size(200.dp), onDraw = 
        drawRoundRect(
            color = myRed,
            style = Stroke(width = 80f),
            cornerRadius = CornerRadius(80f, 80f)
        )
    )

示例结果如下示例2所示,看似好像是空心的,但是AS预览的效果为什么是这样呢,其实是还有一半的画笔宽度也就是40f,是在Canvas外边的,所有使用到Stroke的都有这样的问题。

所以这时候要想正确显示圆角的效果,我们需要自行处理topLeft和size参数了。我们可以看到默认的size参数如下:size: Size = this.size.offsetSize(topLeft),它会减去我们设置的topLeft在x,y偏移量!!!:

private fun Size.offsetSize(offset: Offset): Size =
        Size(this.width - offset.x, this.height - offset.y)

这也太棒了,那我们是不是只处理下topLeft就好了呀,直接设置topLeft参数为topLeft = Offset(40f, 40f),,然而预览效果如下示例3所示:

示例1示例2示例3

这时我们再去看offsetSize这个扩展函数,你仔细琢磨琢磨,应该发现不对劲了!!!

假设我们圆角矩形一开始的宽高width、height都是200f,需要描边Stroke的宽度是80f,那么圆角矩形的左上角起始位置偏移量为x=40f,y=40f,这是没问题的。

那么偏移之后的右下角坐标应该是多少呢,160f(也就是200f-40f),当做坐标是没有问题的,但是关键是绘制的时候使用的是长度,而便宜后的圆角矩形尺寸应该是 200f - 40f - 40f,也就是120f。【写到这里你应该明白示例3的预览为什么是这样了。我不清楚官方的这个offsetSize是否是这么使用的,还是说这是一个bug,写到这里我已经头大了,本来就害怕自定义,哈哈哈哈哈】

那么我们重新写下size参数size = Size(size.width - 40 * 2f, size.height - 40 * 2f),我还是直接把全部代码贴出来吧,大家不知道有没有晕掉:

@Composable
fun DrawReactDemo() 
    Canvas(modifier = Modifier.size(200.dp), onDraw = 
        drawRoundRect(
            color = myRed,
            cornerRadius = CornerRadius(80f, 80f),
            style = Stroke(width = 80f),
            topLeft = Offset(40f, 40f),
            size = Size(size.width - 40 * 2f, size.height - 40 * 2f)
        )
    )

这样的话可以看到正确的预览效果了:

3、版本更新

  • 暂无

4、未解决问题

官方的这个Size.offsetSize扩展函数,我不清楚是我使用的场景不对还是说确实存在bug,如果要修改这个扩展函数,那么应该是偏移量 x2

private fun Size.offsetSize(offset: Offset): Size =
        Size(this.width - 2 * offset.x, this.height - 2 * offset.y)

目前只是跟大家一起研究了最基本的绘制方法,还有一个很重要的参数没说:BlendMode,下一篇继续吧。

以上是关于Jetpack Compose - Canvas的主要内容,如果未能解决你的问题,请参考以下文章

Jetpack Compose - Canvas之BlendMode

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门

Jetpack Compose 从入门到入门

Jetpack Compose 自定义绘制

使用 Jetpack Compose 完成自定义绘制