9、Jetpack Compose 入门 --- 自定义图形Canvas

1,158 阅读6分钟

在Compose中,作用域的概念随处可见,自定义图形也一样。下面一段代码,创建一个Canvas的作用域,并讲其范围指定为父元素中的所有可用空间:

Canvas(modifier = Modifier.fillMaxSize()) {
}

一、绘制图形

  • drawLine:绘制直线
  • drawCircle:绘制圆形
  • drawRect:绘制矩形

1. drawLine(绘制直线)

参数类型描述其他说明
brushBrush线条颜色组合可用于绘制渐变色,与color属性二选一
colorColor线条颜色
startOffset线条起始坐标
endOffset线条终止坐标
strokeWidthFloat线条宽度
capStrokeCap线两头的样式Butt:无头
Round:圆头
Square:方头
pathEffectPathEffect?设置线的显示效果cornerPathEffect:连接线段之间的夹角用一种更平滑的方式连接
dashPathEffect:将线段虚线化
chainPathEffect:可分别设置外部效应和内部效应的路径
stampedPathEffect:使用特定的形状来绘制路径
alphaFloat透明度取值:[0.0, 1.0]
colorFilterColorFilter?颜色过滤器
blendModeBlendMode混合模式这个后面再说

GIF 2022-9-2 17-31-29.gif

@Composable
fun CanvasLine() {
  val pathList: List<Pair<PathEffect, String>> = listOf(
    Pair(PathEffect.cornerPathEffect(6f), "cornerPathEffect"),
    Pair(PathEffect.dashPathEffect(intervals = floatArrayOf(25f, 25f, 5f, 10f)), "dashPathEffect")
  )
  val strokeWidth = remember { mutableStateOf(20) }
  val color = remember { mutableStateOf(Color.Blue) }
  val cap = remember { mutableStateOf(StrokeCap.Butt) }
  val alpha = remember { mutableStateOf(1f) }
  val pathEffect = remember { mutableStateOf(pathList[0].first) }

  Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
    ......
    Canvas(
      modifier = Modifier
        .width(200.dp)
        .height(400.dp),
      onDraw = {
        val canvasWidth = size.width
        val canvasHeight = size.height

        drawLine(
          color = color.value,
          start = Offset(x = canvasWidth * 3 / 4, y = canvasHeight / 4),
          end = Offset(x = canvasWidth / 4, y = canvasHeight * 3 / 4),
          strokeWidth = strokeWidth.value.toFloat(),
          cap = cap.value,
          pathEffect = pathEffect.value,
          alpha = alpha.value
        )
      })
  }
}

2. drawCircle(绘制圆形)

参数类型描述其他说明
brushBrush线条颜色组合可用于绘制渐变色,与color属性二选一
colorColor线条颜色
radiusFloat半径默认为Canvas的宽与高之中较小的值
centerOffset圆心偏移量默认值在Canvas区域的中心点
alphaFloat透明度取值:[0.0, 1.0]
styleDrawStyle图形的绘制风格Fill:实心
Stroke:空心

GIF 2022-9-4 17-30-34.gif

@Composable
fun CanvasCircle() {
  Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
    val color = remember { mutableStateOf(Color.Blue) }
    val radiusParent = remember { mutableStateOf(1f) }
    val centerXOffset = remember { mutableStateOf(0) }
    val centerYOffset = remember { mutableStateOf(0) }
    val alpha = remember { mutableStateOf(1f) }
    val drawStyle: MutableState<DrawStyle> = remember { mutableStateOf(Fill) }
    ......
    Canvas(
      modifier = Modifier.size(200.dp),
      onDraw = {
        drawCircle(
          color = color.value,
          radius = size.minDimension * radiusParent.value / 2.0f,
          center = Offset(this.center.x + centerXOffset.value, this.center.y + centerYOffset.value),
          alpha = alpha.value,
          style = drawStyle.value
        )
      })
  }
}

3. drawRect(绘制矩形)

参数类型描述其他说明
brushBrush线条颜色组合可用于绘制渐变色,与color属性二选一
colorColor线条颜色
topLeftOffset左上角的偏移量默认为(0, 0)
sizeSize矩形的尺寸默认为Canvas的尺寸
alphaFloat透明度取值:[0.0, 1.0]
styleDrawStyle图形的绘制风格Fill:实心
Stroke:空心

GIF 2022-9-4 18-08-40.gif

@Composable
fun CanvasRect() {
  Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
    val color = remember { mutableStateOf(Color.Blue) }
    val topLeftXOffset = remember { mutableStateOf(0) }
    val topLeftYOffset = remember { mutableStateOf(0) }
    val width = remember { mutableStateOf(200) }
    val height = remember { mutableStateOf(200) }
    val alpha = remember { mutableStateOf(1f) }
    val drawStyle: MutableState<DrawStyle> = remember { mutableStateOf(Fill) }
    ......
    Canvas(
      modifier = Modifier.size(200.dp),
      onDraw = {
        drawRect(
          color = color.value,
          topLeft = Offset(topLeftXOffset.value.toFloat(), topLeftYOffset.value.toFloat()),
          size = Size(width = width.value.toFloat(), height = height.value.toFloat()),
          alpha = alpha.value,
          style = drawStyle.value
        )
      })
  }
}

4. drawArc(绘制弧线)

参数类型描述其他说明
brushBrush线条颜色组合可用于绘制渐变色,与color属性二选一
colorColor线条颜色
startAngleFloat起始角度当取值为0时,起始角度为时钟3点钟方向的位置;
当取值为a时,起始角度为时钟3点钟方向按顺时针方向a度的位置。
sweepAngleFloat持续角度弧线持续的角度(顺时针方向为正值)
useCenterBoolean是否绘制弦
topLeftOffset左上角的偏移量默认为(0, 0)
sizeSize尺寸默认为Canvas的尺寸
alphaFloat透明度取值:[0.0, 1.0]
styleDrawStyle图形的绘制风格Fill:实心
Stroke:空心

GIF 2022-9-6 19-38-20.gif

@Composable
fun CanvasArc() {
  val color = remember { mutableStateOf(Color.Blue) }
  val startAngle = remember { mutableStateOf(0f) }
  val sweepAngle = remember { mutableStateOf(180f) }
  val useCenter = remember { mutableStateOf(false) }
  val topLeftXOffset = remember { mutableStateOf(0) }
  val topLeftYOffset = remember { mutableStateOf(0) }
  val width = remember { mutableStateOf(200) }
  val height = remember { mutableStateOf(200) }
  val alpha = remember { mutableStateOf(1f) }
  val drawStyle: MutableState<DrawStyle> = remember { mutableStateOf(Stroke(width = 8f)) }

  Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
    ......
    Canvas(
      modifier = Modifier
        .background(Color.LightGray)
        .width(200.dp)
        .height(200.dp),
      onDraw = {
        drawArc(
          color = color.value,
          startAngle = startAngle.value,
          sweepAngle = sweepAngle.value,
          useCenter = useCenter.value,
          topLeft = Offset(topLeftXOffset.value.toFloat(), topLeftYOffset.value.toFloat()),
          size = Size(width.value.dp.toPx(), height.value.dp.toPx()),
          alpha = alpha.value,
          style = drawStyle.value
        )
      })
  }
}

5. drawPath(绘制路径)

参数类型描述其他说明
pathPath路径绘制的路径
brushBrush线条颜色组合可用于绘制渐变色,与color属性二选一
colorColor线条颜色
alphaFloat透明度取值:[0.0, 1.0]
styleDrawStyle图形的绘制风格Fill:实心
Stroke:空心

GIF 2022-9-6 20-08-44.gif

@Composable
fun CanvasCustom() {
  Column(modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally) {
    val color = remember { mutableStateOf(Color.Blue) }
    val alpha = remember { mutableStateOf(1f) }
    val drawStyle: MutableState<DrawStyle> = remember { mutableStateOf(Stroke(width = 8f)) }
    ......
    Canvas(
      modifier = Modifier
        .background(Color.LightGray)
        .size(200.dp),
      onDraw = {
        //绘制一个五角星
        //指定五角星的5个顶点坐标
        val point1 = Offset(100.dp.toPx(), 0.dp.toPx())
        val point2 = Offset(38.dp.toPx(), 200.dp.toPx())
        val point3 = Offset(200.dp.toPx(), 78.dp.toPx())
        val point4 = Offset(0.dp.toPx(), 78.dp.toPx())
        val point5 = Offset(162.dp.toPx(), 200.dp.toPx())

        drawPath(
          path = Path().apply {
            //移动到起始点
            moveTo(point1.x, point1.y)
            //绘制直线
            lineTo(point2.x, point2.y)
            lineTo(point3.x, point3.y)
            lineTo(point4.x, point4.y)
            lineTo(point5.x, point5.y)
            //封闭图形(回到起始点)
            close()
          },
          color = color.value,
          alpha = alpha.value,
          style = drawStyle.value
        )
      })
  }
}

二、图形变换

属性描述其他说明
scale缩放参数中的scale、scaleX、scaleY均表示缩放的倍数
translate平移参数中的left、top分别表示向左/向上平移的像素
rotate旋转参数中degrees表示按顺时针选转的角度,pivot表的旋转的中心点
inset边衬区可更改绘制边界和平移绘制,为绘制的图形添加内边距
withTransform组合多个变换

动画.gif

Canvas(
  modifier = Modifier.size(200.dp)
) {
  withTransform({
    scale(scale = scale.value, pivot = center)
    translate(left = translateX.value, top = translateY.value)
    rotate(degrees = degrees.value, pivot = center)
  }) {
    drawImage(image = imageBitmap)
  }
}

只需要单独使用其中一种变换方式的话,可以这样:

scale(scale = scale.value, pivot = center) {
  drawImage(image = imageBitmap)
}

translate(left = translateX.value, top = translateY.value) {
  drawImage(image = imageBitmap)
}

rotate(degrees = degrees.value, pivot = center) {
  drawImage(image = imageBitmap)
}

三、渐变Brush

颜色渐变的方式有很多种,比如线性渐变,发散渐变等。各种方式都有着对应的api,但总结起来所需要的参数无非就是如下三种:颜色集合、起始位置、平铺模式。

fun xxxGradient(颜色集合, 颜色集合作用的起始范围, tileMode(平铺模式))
  1. 颜色集合:在起始范围内的颜色渐变顺序。其传参类型有两种选择,Pair<Float, Color>List<Color>,前者可根据Float的数值对每个颜色的占比进行分配,后者表示每个颜色渐变中所在的位置是以起始位置(0)到终止位置(1)进行均分的位置。

    //起始位置为黄色,到20%的位置渐变为红色,再到结束的位置渐变为蓝色
    val colorStops = arrayOf(
        0.0f to Color.Yellow,
        0.2f to Color.Red,
        1f to Color.Blue
    )
    
    //如下两种方式等价:
    //起始位置为黄色,到20%的位置渐变为红色,再到结束的位置渐变为蓝色
    val colors = listOf(Color.Yellow, Color.Red, Color.Blue)
    val colors = arrayOf(
        0.0f to Color.Yellow,
        0.5f to Color.Red,
        1f to Color.Blue
    )
    
  2. tileMode:可选值有ClampRepeatedMirrorDecal

    • Clamp:边缘固定为最终的颜色。然后,它会将区域的剩余空间绘制为距离最近的颜色。
    • Repeated:边缘按从最后一种颜色到第一种颜色的顺序镜像。
    • Mirror:边缘按从最后一种颜色到第一种颜色的顺序镜像。
    • Decal:仅在边界大小的范围内渲染。TileMode.Decal 利用透明的黑色对原始边界以外的内容进行采样,而 TileMode.Clamp 对边缘颜色进行采样。

    image.png

  3. 方向

    • linearGradient:线性
    • horizontalGradient:横向
    • verticalGradient:纵向
    • radialGradient:径向
    • sweepGradient:发散

    image.png