这一节主要了解一下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
)
}
- 样式控制
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)
- 变换操作
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)
}
- 组合效果
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方法,应避免在其中创建新对象;