Compose系列文章,请点原文阅读。原文,是时候学习Compose了!
Canvas 是允许您在屏幕上指定区域并在此区域上执行绘制的组件。您必须使用修饰符指定尺寸,无论是通过Modifier.size修饰符指定确切尺寸,还是通过Modifier.fillMaxSize,ColumnScope.weight等相对于父级指定精确尺寸。如果父级包装了此子级,则仅必须指定确切尺寸。
【目前基于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
在以上函数中,除了drawImage函数没有color参数,其他的函数都提供了color和brush的两种方式,关于brush请查看之前的Modifier相关文章,一下文章我们只使用color来进行演示。
具体的函数及参数请查看源码,本文只实现其必选参数完成演示,并不一定会用到所有的参数。
该函数必须实现的参数有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 |
---|---|---|
绘制圆形,实心圆形和空心圆形如下代码所示:
@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 |
---|---|
绘制图片,刚开始只知道需要一个实现了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
)
})
}
绘制线段,代码如下所示:
@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
参数后,效果如下图右侧线段所示:
绘制椭圆:
@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 |
---|---|---|
根据路径绘制相关图形,需要使用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 |
---|---|
绘制点,我们为了演示效果,使用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 |
---|---|
绘制矩形和圆角矩形类似,我们这里就使用圆角矩形来进行演示了,如下代码所示:
@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)
)
})
}
官方的这个Size.offsetSize
扩展函数,我不清楚是我使用的场景不对还是说确实存在bug,如果要修改这个扩展函数,那么应该是偏移量 x2。
private fun Size.offsetSize(offset: Offset): Size =
Size(this.width - 2 * offset.x, this.height - 2 * offset.y)
目前只是跟大家一起研究了最基本的绘制方法,还有一个很重要的参数没说:BlendMode
,下一篇继续吧。