Compose笔记(二十四)--Canvas

这一节主要了解一下Compose中Canvas,在Jetpack Compose中,Canvas是用于创建自定义绘图和动画的核心组件,它提供了强大的API来绘制各种图形、图像和效果。简单总给如下:

API

1.1 基本图形

Kotlin 复制代码
// 绘制圆形
drawCircle(color = Color.Red, radius = 50f)

// 绘制矩形
drawRect(color = Color.Blue, size = Size(100f, 150f))

// 绘制圆角矩形
drawRoundRect(
    color = Color.Green,
    cornerRadius = CornerRadius(10f, 10f)
)

// 绘制椭圆
drawOval(color = Color.Yellow, topLeft = Offset(50f, 50f))

// 绘制线条
drawLine(
    color = Color.Black,
    start = Offset(0f, 0f),
    end = Offset(100f, 100f),
    strokeWidth = 5f
)

1.2 路径 (Path)

Kotlin 复制代码
val path = Path().apply {
    moveTo(50f, 50f)
    lineTo(150f, 50f)
    lineTo(100f, 150f)
    close() // 闭合路径
}

drawPath(
    path = path,
    color = Color.Magenta,
    style = Fill // 填充
)

// 或描边
drawPath(
    path = path,
    color = Color.Black,
    style = Stroke(width = 3f)
)

1.3 文本

Kotlin 复制代码
drawIntoCanvas { canvas ->
    val paint = Paint().apply {
        color = Color.Black
        textSize = 24.sp.toPx()
        textAlign = android.graphics.Paint.Align.CENTER
    }
    
    canvas.nativeCanvas.drawText(
        "Hello Canvas",
        size.width / 2,
        size.height / 2,
        paint
    )
}
  1. 样式控制

2.1 画笔样式

Kotlin 复制代码
// 填充样式
drawCircle(
    color = Color.Red,
    radius = 50f,
    style = Fill
)

// 描边样式
drawCircle(
    color = Color.Blue,
    radius = 60f,
    style = Stroke(width = 5f)
)

// 描边+圆角
drawCircle(
    color = Color.Green,
    radius = 70f,
    style = Stroke(
        width = 10f,
        cap = StrokeCap.Round, // 圆角端点
        join = StrokeJoin.Round // 圆角连接
    )
)

2.2 渐变

Kotlin 复制代码
// 线性渐变
val brush = LinearGradientBrush(
    start = Offset(0f, 0f),
    end = Offset(size.width, size.height),
    colors = listOf(Color.Red, Color.Blue)
)

drawRect(brush = brush, size = size)

// 径向渐变
val radialBrush = RadialGradientBrush(
    center = Offset(size.width / 2, size.height / 2),
    radius = size.minDimension / 2,
    colors = listOf(Color.Yellow, Color.Transparent)
)

drawCircle(brush = radialBrush, radius = size.minDimension / 2)
  1. 变换操作

3.1 平移、旋转、缩放

Kotlin 复制代码
withTransform({
    translate(left = 50f, top = 50f) // 平移
    rotate(degrees = 45f) // 旋转
    scale(scaleX = 1.5f, scaleY = 1.5f) // 缩放
}) {
    drawCircle(color = Color.Red, radius = 30f)
}

3.2 裁剪

Kotlin 复制代码
// 裁剪为圆形区域
clipPath(Path().apply {
    addOval(
        Rect(
            left = size.width / 4,
            top = size.height / 4,
            right = size.width * 3 / 4,
            bottom = size.height * 3 / 4
        )
    )
}) {
    drawRect(color = Color.Blue, size = size)
}
  1. 组合效果

4.1 阴影

Kotlin 复制代码
drawCircle(
    color = Color.Red,
    radius = 50f,
    shadowColor = Color.Black.copy(alpha = 0.5f),
    blurRadius = 10f,
    style = Fill
)

4.2 混合模式

Kotlin 复制代码
drawIntoCanvas { canvas ->
    canvas.saveLayer(Rect(0f, 0f, size.width, size.height), null)    
    // 绘制底层
    drawCircle(color = Color.Red, radius = 60f)  
    // 设置混合模式
    canvas.nativeCanvas.compositeMode = PorterDuff.Mode.SRC_IN   
    // 绘制上层
    drawCircle(color = Color.Blue, radius = 40f)
    canvas.restore()
}

栗子:

Kotlin 复制代码
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp

@Composable
fun GridBackground(
    cellSize: Dp = 30.dp,
    lineColor: Color = Color.LightGray.copy(alpha = 0.5f),
    lineWidth: Float = 1f
) {
    val cellPx = cellSize.value

    Canvas(modifier = Modifier.fillMaxSize()) {
        val width = size.width
        val height = size.height

        // 绘制水平线
        for (y in 0..height.toInt() step cellPx.toInt()) {
            drawLine(
                color = lineColor,
                start = Offset(0f, y.toFloat()),
                end = Offset(width, y.toFloat()),
                strokeWidth = lineWidth
            )
        }

        // 绘制垂直线
        for (x in 0..width.toInt() step cellPx.toInt()) {
            drawLine(
                color = lineColor,
                start = Offset(x.toFloat(), 0f),
                end = Offset(x.toFloat(), height),
                strokeWidth = lineWidth
            )
        }

        // 绘制对角线
        drawLine(
            color = lineColor,
            start = Offset(0f, 0f),
            end = Offset(width, height),
            strokeWidth = lineWidth
        )

        drawLine(
            color = lineColor,
            start = Offset(width, 0f),
            end = Offset(0f, height),
            strokeWidth = lineWidth
        )
    }
}
Kotlin 复制代码
import androidx.compose.animation.core.*
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun RippleEffect() {
    val scope = rememberCoroutineScope()
    val ripples = remember { mutableStateListOf<Ripple>() }

    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            val ripple = Ripple()
            ripples.add(ripple)

            // 在协程中启动动画
            scope.launch {
                ripple.animatable.animateTo(
                    targetValue = 1f,
                    animationSpec = tween(2000, easing = LinearEasing)
                )
            }

            if (ripples.size > 3) {
                ripples.removeAt(0)
            }
        }
    }

    Canvas(modifier = Modifier.fillMaxSize()) {
        val center = Offset(size.width / 2, size.height / 2)

        ripples.forEach { ripple ->
            val radius = ripple.animatable.value * size.minDimension * 0.3f
            val alpha = 1f - ripple.animatable.value
            if(radius>0) {
                drawCircle(
                    brush = Brush.radialGradient(
                        colors = listOf(
                            Color(0xFF42A5F5).copy(alpha = alpha * 0.7f),
                            Color(0xFF42A5F5).copy(alpha = 0f)
                        ),
                        center = center,
                        radius = radius
                    ),
                    radius = radius,
                    center = center
                )
            }
        }
    }
}

注意:

1 避免阻塞UI线程,复杂计算应在后台协程中进行,只将结果传递给Canvas;

2 动画必须在协程中运行,Animatable、animate*AsState需在协程作用域内启动;

3 避免在绘制过程中创建对象,每次重组都会调用draw方法,应避免在其中创建新对象;

相关推荐
海棠蚀omo1 小时前
C++笔记-红黑树
开发语言·c++·笔记
龙湾开发2 小时前
计算机图形学编程(使用OpenGL和C++)(第2版)学习笔记 12.曲面细分
c++·笔记·学习·3d·图形渲染
霸王蟹2 小时前
React中巧妙使用异步组件Suspense优化页面性能。
前端·笔记·学习·react.js·前端框架
霸王蟹3 小时前
React 19 中的useRef得到了进一步加强。
前端·javascript·笔记·学习·react.js·ts
霸王蟹3 小时前
React 19版本refs也支持清理函数了。
前端·javascript·笔记·react.js·前端框架·ts
Vizio<4 小时前
基于CNN的猫狗识别(自定义CNN模型)
人工智能·笔记·深度学习·神经网络·cnn
N_NAN_N4 小时前
程序设计语言----软考中级软件设计师(自用学习笔记)
笔记·学习
我的老子姓彭6 小时前
LWIP的NETCONN接口
笔记·lwip
幼稚诠释青春6 小时前
Java API学习笔记
笔记·学习