Graphics và Canvas trong Jetpack Compose
Compose cung cấp APIs mạnh mẽ để vẽ custom graphics, từ shapes đơn giản đến drawings phức tạp.
1. Canvas Composable
Cơ bản
@Composable
fun SimpleCanvas() {
Canvas(modifier = Modifier.size(200.dp)) {
// DrawScope - vẽ ở đây
drawRect(color = Color.Blue)
}
}DrawScope properties
Canvas(modifier = Modifier.size(200.dp)) {
val width = size.width // Chiều rộng canvas
val height = size.height // Chiều cao canvas
val center = this.center // Tâm canvas
// Vẽ tại center
drawCircle(
color = Color.Red,
radius = 50f,
center = center
)
}2. Vẽ Shapes cơ bản
Rectangle
Canvas(modifier = Modifier.size(200.dp)) {
// Filled rectangle
drawRect(color = Color.Blue)
// Rectangle với size và offset
drawRect(
color = Color.Red,
topLeft = Offset(20f, 20f),
size = Size(100f, 80f)
)
// Outlined rectangle
drawRect(
color = Color.Green,
style = Stroke(width = 4f)
)
}Circle và Oval
Canvas(modifier = Modifier.size(200.dp)) {
// Circle
drawCircle(
color = Color.Blue,
radius = 80f,
center = center
)
// Oval
drawOval(
color = Color.Red,
topLeft = Offset(10f, 50f),
size = Size(180f, 100f)
)
}Line
Canvas(modifier = Modifier.size(200.dp)) {
drawLine(
color = Color.Blue,
start = Offset(0f, 0f),
end = Offset(size.width, size.height),
strokeWidth = 4f
)
// Dashed line
drawLine(
color = Color.Red,
start = Offset(0f, size.height),
end = Offset(size.width, 0f),
strokeWidth = 4f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f))
)
}Arc
Canvas(modifier = Modifier.size(200.dp)) {
// Arc (partial circle)
drawArc(
color = Color.Blue,
startAngle = 0f,
sweepAngle = 270f, // Vẽ 270 độ
useCenter = true, // Nối với tâm thành hình quạt
topLeft = Offset(20f, 20f),
size = Size(160f, 160f)
)
}3. Path - Vẽ hình dạng tùy ý
Path cơ bản
Canvas(modifier = Modifier.size(200.dp)) {
val path = Path().apply {
moveTo(0f, size.height) // Start bottom-left
lineTo(size.width / 2, 0f) // Go to top-center
lineTo(size.width, size.height) // Go to bottom-right
close() // Close path (tam giác)
}
drawPath(path, color = Color.Blue)
}Bezier curves
Canvas(modifier = Modifier.size(200.dp)) {
val path = Path().apply {
moveTo(0f, size.height / 2)
// Quadratic Bezier (1 control point)
quadraticBezierTo(
x1 = size.width / 2, y1 = 0f, // Control point
x2 = size.width, y2 = size.height / 2 // End point
)
}
drawPath(
path = path,
color = Color.Blue,
style = Stroke(width = 4f)
)
}Cubic Bezier
Canvas(modifier = Modifier.size(200.dp)) {
val path = Path().apply {
moveTo(0f, size.height)
// Cubic Bezier (2 control points)
cubicTo(
x1 = size.width * 0.25f, y1 = 0f, // Control point 1
x2 = size.width * 0.75f, y2 = size.height, // Control point 2
x3 = size.width, y3 = 0f // End point
)
}
drawPath(
path = path,
color = Color.Blue,
style = Stroke(width = 4f)
)
}4. Brush - Gradients và Patterns
Linear Gradient
Canvas(modifier = Modifier.size(200.dp)) {
drawRect(
brush = Brush.linearGradient(
colors = listOf(Color.Red, Color.Yellow, Color.Green),
start = Offset.Zero,
end = Offset(size.width, size.height)
)
)
}Radial Gradient
Canvas(modifier = Modifier.size(200.dp)) {
drawCircle(
brush = Brush.radialGradient(
colors = listOf(Color.White, Color.Blue),
center = center,
radius = size.minDimension / 2
)
)
}Sweep Gradient
Canvas(modifier = Modifier.size(200.dp)) {
drawCircle(
brush = Brush.sweepGradient(
colors = listOf(Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Red),
center = center
)
)
}Gradient với color stops
Canvas(modifier = Modifier.size(200.dp)) {
drawRect(
brush = Brush.horizontalGradient(
colorStops = arrayOf(
0.0f to Color.Red,
0.3f to Color.Yellow,
1.0f to Color.Green
)
)
)
}5. Text trên Canvas
Canvas(modifier = Modifier.size(200.dp)) {
drawContext.canvas.nativeCanvas.apply {
drawText(
"Hello Canvas",
center.x,
center.y,
android.graphics.Paint().apply {
textSize = 40f
color = android.graphics.Color.BLUE
textAlign = android.graphics.Paint.Align.CENTER
}
)
}
}Với TextMeasurer (Compose cách)
@Composable
fun TextOnCanvas() {
val textMeasurer = rememberTextMeasurer()
Canvas(modifier = Modifier.size(200.dp)) {
drawText(
textMeasurer = textMeasurer,
text = "Hello Compose",
topLeft = Offset(10f, 10f),
style = TextStyle(
fontSize = 20.sp,
color = Color.Blue
)
)
}
}6. Transformations
Scale, Rotate, Translate
Canvas(modifier = Modifier.size(200.dp)) {
withTransform({
translate(left = 50f, top = 50f)
rotate(degrees = 45f, pivot = Offset(50f, 50f))
scale(scaleX = 1.5f, scaleY = 1.5f, pivot = Offset(50f, 50f))
}) {
drawRect(
color = Color.Blue,
topLeft = Offset.Zero,
size = Size(100f, 100f)
)
}
}inset
Canvas(modifier = Modifier.size(200.dp)) {
// Draw full size
drawRect(color = Color.LightGray)
// Draw with inset
inset(20f, 20f, 20f, 20f) {
drawRect(color = Color.Blue)
}
}7. Blend Modes
Canvas(modifier = Modifier.size(200.dp)) {
drawCircle(
color = Color.Red,
radius = 80f,
center = Offset(80f, 100f)
)
drawCircle(
color = Color.Blue,
radius = 80f,
center = Offset(120f, 100f),
blendMode = BlendMode.Multiply // Blend với circle phía dưới
)
}Các BlendMode phổ biến
| Mode | Mô tả |
|---|---|
SrcOver | Default - vẽ đè lên |
Multiply | Nhân màu |
Screen | Sáng hơn |
Overlay | Kết hợp Multiply và Screen |
Difference | Hiệu màu |
Clear | Xóa vùng |
8. drawWithContent và drawBehind
drawBehind - Vẽ phía sau content
@Composable
fun CardWithShadow(content: @Composable () -> Unit) {
Box(
modifier = Modifier
.drawBehind {
// Vẽ shadow phía sau
drawRoundRect(
color = Color.Gray.copy(alpha = 0.3f),
topLeft = Offset(4.dp.toPx(), 4.dp.toPx()),
cornerRadius = CornerRadius(8.dp.toPx())
)
}
.background(Color.White, RoundedCornerShape(8.dp))
) {
content()
}
}drawWithContent - Vẽ trước hoặc sau content
@Composable
fun GradientOverlay() {
Box(
modifier = Modifier
.size(200.dp)
.drawWithContent {
drawContent() // Vẽ content trước
// Vẽ gradient overlay lên trên
drawRect(
brush = Brush.verticalGradient(
colors = listOf(Color.Transparent, Color.Black.copy(alpha = 0.5f))
)
)
}
) {
Image(
painter = painterResource(R.drawable.image),
contentDescription = null
)
}
}9. Custom Progress Indicator
@Composable
fun CircularProgress(
progress: Float, // 0 to 1
modifier: Modifier = Modifier
) {
Canvas(modifier = modifier.size(100.dp)) {
val strokeWidth = 8.dp.toPx()
val diameter = size.minDimension - strokeWidth
// Background circle
drawCircle(
color = Color.LightGray,
radius = diameter / 2,
center = center,
style = Stroke(width = strokeWidth)
)
// Progress arc
drawArc(
color = Color.Blue,
startAngle = -90f,
sweepAngle = progress * 360f,
useCenter = false,
topLeft = Offset(strokeWidth / 2, strokeWidth / 2),
size = Size(diameter, diameter),
style = Stroke(
width = strokeWidth,
cap = StrokeCap.Round
)
)
}
}10. Animated Graphics
@Composable
fun AnimatedWave() {
val infiniteTransition = rememberInfiniteTransition()
val phase by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = 2 * Math.PI.toFloat(),
animationSpec = infiniteRepeatable(
animation = tween(2000, easing = LinearEasing)
)
)
Canvas(modifier = Modifier.fillMaxWidth().height(100.dp)) {
val path = Path()
path.moveTo(0f, size.height / 2)
for (x in 0..size.width.toInt()) {
val y = (size.height / 2) +
(30 * sin((x * 0.02) + phase)).toFloat()
path.lineTo(x.toFloat(), y)
}
drawPath(
path = path,
color = Color.Blue,
style = Stroke(width = 4f)
)
}
}📝 Tóm tắt
| Drawing | Function |
|---|---|
| Rectangle | drawRect() |
| Circle | drawCircle() |
| Oval | drawOval() |
| Line | drawLine() |
| Arc | drawArc() |
| Path | drawPath() |
| Image | drawImage() |
| Text | drawText() |
Modifiers cho drawing
| Modifier | Mục đích |
|---|---|
drawBehind | Vẽ phía sau content |
drawWithContent | Vẽ trước/sau content |
drawWithCache | Cache drawing objects |
Last updated on