HarmonyOS APP开发:Drawing绘图API与图形操作
【摘要】 HarmonyOS APP开发:Drawing绘图API与图形操作📌 核心要点:Drawing API是HarmonyOS提供的2D图形绘制接口,包含Canvas、Paint、Path等核心类,支持矢量图形、位图操作、特效处理等完整绘图能力。 一、背景与动机 1.1 为什么需要Drawing API在应用开发中,图形绘制是构建丰富视觉体验的基础能力。传统绘图方案存在诸多限制:平台API碎...
HarmonyOS APP开发:Drawing绘图API与图形操作
📌 核心要点:Drawing API是HarmonyOS提供的2D图形绘制接口,包含Canvas、Paint、Path等核心类,支持矢量图形、位图操作、特效处理等完整绘图能力。
一、背景与动机
1.1 为什么需要Drawing API
在应用开发中,图形绘制是构建丰富视觉体验的基础能力。传统绘图方案存在诸多限制:
- 平台API碎片化:Android Canvas、iOS CoreGraphics、Web Canvas 2D各自独立,学习成本高
- 功能受限:基础图形API难以实现复杂效果,需引入第三方库
- 性能瓶颈:频繁绘制操作导致CPU负载过高,影响流畅度
- 特效支持不足:滤镜、混合模式、着色器等高级特性支持有限
HarmonyOS Drawing API提供统一的跨平台绘图能力:
classDef coreApi fill:#e3f2fd,stroke:#1565c0,stroke-width:2px
classDef shapeApi fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
classDef effectApi fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
classDef imageApi fill:#fff3e0,stroke:#e65100,stroke-width:2px
flowchart TB
subgraph DrawingAPI[Drawing API体系]
Canvas[Canvas<br/>画布核心]:::coreApi
Paint[Paint<br/>画笔样式]:::coreApi
Path[Path<br/>路径绘制]:::coreApi
Shape[Shape<br/>基础图形]:::shapeApi
Transform[Transform<br/>变换操作]:::shapeApi
Clip[Clip<br/>裁剪区域]:::shapeApi
Shader[Shader<br/>着色器]:::effectApi
Filter[Filter<br/>滤镜效果]:::effectApi
Blend[BlendMode<br/>混合模式]:::effectApi
Bitmap[Bitmap<br/>位图操作]:::imageApi
Image[Image<br/>图像处理]:::imageApi
end
Canvas --> Shape
Canvas --> Transform
Canvas --> Clip
Paint --> Shader
Paint --> Filter
Paint --> Blend
1.2 Drawing API设计理念
Drawing API遵循声明式绘图设计原则:
| 设计原则 | 说明 | 优势 |
|---|---|---|
| 状态机模式 | Paint保存绘制状态,Canvas执行绘制 | 状态管理清晰 |
| 链式调用 | 支持方法链式配置 | 代码简洁易读 |
| 类型安全 | ArkTS强类型约束 | 编译期错误检查 |
| 硬件加速 | 自动利用GPU加速 | 高性能渲染 |
二、核心原理
2.1 Drawing API核心类关系
classDef canvasClass fill:#bbdefb,stroke:#1565c0,stroke-width:3px
classDef paintClass fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
classDef pathClass fill:#ffccbc,stroke:#d84315,stroke-width:2px
classDef shaderClass fill:#e1bee7,stroke:#7b1fa2,stroke-width:2px
classDiagram
class Canvas {
+drawRect(rect, paint)
+drawCircle(cx, cy, radius, paint)
+drawPath(path, paint)
+drawText(text, x, y, paint)
+drawBitmap(bitmap, left, top, paint)
+clipPath(path)
+save()
+restore()
+translate(dx, dy)
+scale(sx, sy)
+rotate(degrees)
+concat(matrix)
}
class Paint {
-color: Color
-style: Style
-strokeWidth: number
-antiAlias: boolean
-shader: Shader
-maskFilter: MaskFilter
-colorFilter: ColorFilter
+setColor(color)
+setStyle(style)
+setStrokeWidth(width)
+setAntiAlias(aa)
+setShader(shader)
+setMaskFilter(filter)
+setColorFilter(filter)
}
class Path {
+moveTo(x, y)
+lineTo(x, y)
+quadTo(x1, y1, x2, y2)
+cubicTo(x1, y1, x2, y2, x3, y3)
+arcTo(rect, startAngle, sweepAngle)
+addRect(rect)
+addCircle(cx, cy, radius)
+addRoundRect(rect, rx, ry)
+close()
+reset()
}
class Shader {
+createLinearGradient()
+createRadialGradient()
+createSweepGradient()
+createBitmapShader()
}
Canvas --> Paint : 使用
Canvas --> Path : 绘制
Paint --> Shader : 填充
2.2 绘制流程解析
Drawing API的绘制流程分为四个阶段:
阶段一:准备阶段
// 创建画布和画笔
const canvas = new Canvas(); // 或从Component获取
const paint = new Paint(); // 创建画笔对象
// 配置画笔属性
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
paint.setAntiAlias(true);
阶段二:状态保存
// 保存当前状态(变换矩阵、裁剪区域等)
canvas.save();
// 应用变换
canvas.translate(100, 100); // 平移
canvas.rotate(45); // 旋转
canvas.scale(1.5, 1.5); // 缩放
阶段三:绘制执行
// 绘制各种图形
canvas.drawRect(0, 0, 100, 100, paint);
canvas.drawCircle(50, 50, 30, paint);
// 绘制路径
const path = new Path();
path.moveTo(0, 0);
path.lineTo(100, 100);
canvas.drawPath(path, paint);
阶段四:状态恢复
// 恢复到保存时的状态
canvas.restore();
2.3 坐标系统与变换
classDef coordNode fill:#fff8e1,stroke:#f57f17,stroke-width:2px
classDef transNode fill:#e0f2f1,stroke:#00695c,stroke-width:2px
flowchart LR
subgraph 坐标系统
World[世界坐标<br/>屏幕绝对位置]:::coordNode
Local[局部坐标<br/>组件相对位置]:::coordNode
Device[设备坐标<br/>物理像素位置]:::coordNode
end
subgraph 变换操作
Translate[平移<br/>translate]:::transNode
Scale[缩放<br/>scale]:::transNode
Rotate[旋转<br/>rotate]:::transNode
Skew[倾斜<br/>skew]:::transNode
end
World --> Translate --> Local
Local --> Scale --> Rotate
Rotate --> Skew --> Device
三、代码实战
3.1 基础图形绘制
// 文件:BasicShapeDrawing.ets
import { Canvas, Paint, Color, Path } from '@ohos.graphics';
@Entry
@Component
struct BasicShapeDrawing {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.onReady(() => {
this.drawBasicShapes();
})
}
.width('100%')
.height('100%')
}
// 绘制基础图形
private drawBasicShapes(): void {
const ctx = this.context;
// 清空画布
ctx.clearRect(0, 0, ctx.width, ctx.height);
// 设置背景
ctx.fillStyle = '#1a1a2e';
ctx.fillRect(0, 0, ctx.width, ctx.height);
// ========== 绘制矩形 ==========
// 实心矩形
ctx.fillStyle = '#667eea';
ctx.fillRect(50, 50, 120, 80);
// 描边矩形
ctx.strokeStyle = '#764ba2';
ctx.lineWidth = 3;
ctx.strokeRect(200, 50, 120, 80);
// 圆角矩形(使用路径)
this.drawRoundRect(ctx, 50, 160, 120, 80, 15, '#f093fb');
// ========== 绘制圆形与椭圆 ==========
// 实心圆
ctx.fillStyle = '#4facfe';
ctx.beginPath();
ctx.arc(310, 100, 40, 0, Math.PI * 2);
ctx.fill();
// 描边圆
ctx.strokeStyle = '#00f2fe';
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(410, 100, 40, 0, Math.PI * 2);
ctx.stroke();
// 椭圆
ctx.fillStyle = '#fa709a';
ctx.beginPath();
ctx.ellipse(110, 350, 60, 35, 0, 0, Math.PI * 2);
ctx.fill();
// ========== 绘制线条与多边形 ==========
// 直线
ctx.strokeStyle = '#fee140';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(250, 280);
ctx.lineTo(400, 350);
ctx.stroke();
// 折线
ctx.strokeStyle = '#30cfd0';
ctx.beginPath();
ctx.moveTo(50, 400);
ctx.lineTo(150, 450);
ctx.lineTo(100, 500);
ctx.lineTo(200, 480);
ctx.stroke();
// 三角形
ctx.fillStyle = '#a8edea';
ctx.beginPath();
ctx.moveTo(350, 400);
ctx.lineTo(300, 480);
ctx.lineTo(400, 480);
ctx.closePath();
ctx.fill();
// ========== 绘制弧形与扇形 ==========
// 弧形
ctx.strokeStyle = '#fed6e3';
ctx.lineWidth = 8;
ctx.beginPath();
ctx.arc(500, 100, 50, 0, Math.PI * 1.5);
ctx.stroke();
// 扇形
ctx.fillStyle = '#d299c2';
ctx.beginPath();
ctx.moveTo(500, 280);
ctx.arc(500, 280, 60, 0, Math.PI * 1.25);
ctx.closePath();
ctx.fill();
}
// 绘制圆角矩形辅助方法
private drawRoundRect(
ctx: CanvasRenderingContext2D,
x: number, y: number,
width: number, height: number,
radius: number,
color: string
): void {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.arcTo(x + width, y, x + width, y + radius, radius);
ctx.lineTo(x + width, y + height - radius);
ctx.arcTo(x + width, y + height, x + width - radius, y + height, radius);
ctx.lineTo(x + radius, y + height);
ctx.arcTo(x, y + height, x, y + height - radius, radius);
ctx.lineTo(x, y + radius);
ctx.arcTo(x, y, x + radius, y, radius);
ctx.closePath();
ctx.fill();
}
}
3.2 路径绘制与贝塞尔曲线
// 文件:PathDrawing.ets
import { Canvas, Paint, Color, Path } from '@ohos.graphics';
@Entry
@Component
struct PathDrawing {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.onReady(() => {
this.drawPaths();
})
}
.width('100%')
.height('100%')
}
private drawPaths(): void {
const ctx = this.context;
// 清空并设置背景
ctx.fillStyle = '#0f0f23';
ctx.fillRect(0, 0, ctx.width, ctx.height);
// ========== 绘制心形路径 ==========
this.drawHeartPath(ctx, 150, 150, 80);
// ========== 绘制星形路径 ==========
this.drawStarPath(ctx, 350, 150, 60, 5);
// ========== 绘制波浪路径(贝塞尔曲线)==========
this.drawWavePath(ctx, 50, 280, 500, 60);
// ========== 绘制花瓣路径 ==========
this.drawFlowerPath(ctx, 150, 450, 50, 6);
// ========== 绘制螺旋路径 ==========
this.drawSpiralPath(ctx, 400, 450, 5, 80);
}
// 绘制心形
private drawHeartPath(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
size: number
): void {
ctx.save();
ctx.translate(cx, cy);
// 创建渐变填充
const gradient = ctx.createLinearGradient(-size, -size, size, size);
gradient.addColorStop(0, '#ff6b6b');
gradient.addColorStop(1, '#ee5a24');
ctx.fillStyle = gradient;
ctx.beginPath();
// 使用贝塞尔曲线绘制心形
ctx.moveTo(0, size * 0.3);
ctx.bezierCurveTo(
-size, -size * 0.5,
-size * 0.5, -size,
0, -size * 0.5
);
ctx.bezierCurveTo(
size * 0.5, -size,
size, -size * 0.5,
0, size * 0.3
);
ctx.fill();
// 添加光泽效果
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.beginPath();
ctx.ellipse(-size * 0.3, -size * 0.3, size * 0.2, size * 0.15, Math.PI / 4, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// 绘制星形
private drawStarPath(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
radius: number,
points: number
): void {
const gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, radius);
gradient.addColorStop(0, '#ffd32a');
gradient.addColorStop(1, '#ff9f1a');
ctx.fillStyle = gradient;
ctx.beginPath();
const innerRadius = radius * 0.4;
const step = Math.PI / points;
for (let i = 0; i < points * 2; i++) {
const r = i % 2 === 0 ? radius : innerRadius;
const angle = i * step - Math.PI / 2;
const x = cx + r * Math.cos(angle);
const y = cy + r * Math.sin(angle);
if (i === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
}
ctx.closePath();
ctx.fill();
// 描边
ctx.strokeStyle = '#f368e0';
ctx.lineWidth = 2;
ctx.stroke();
}
// 绘制波浪(二次贝塞尔曲线)
private drawWavePath(
ctx: CanvasRenderingContext2D,
x: number, y: number,
width: number, amplitude: number
): void {
ctx.save();
// 绘制多层波浪
const layers = [
{ color: 'rgba(102, 126, 234, 0.8)', offset: 0, amp: amplitude },
{ color: 'rgba(118, 75, 162, 0.6)', offset: 20, amp: amplitude * 0.7 },
{ color: 'rgba(240, 147, 251, 0.4)', offset: 40, amp: amplitude * 0.5 }
];
for (const layer of layers) {
ctx.fillStyle = layer.color;
ctx.beginPath();
ctx.moveTo(x, y + layer.offset + layer.amp);
// 使用二次贝塞尔曲线绘制波浪
const segments = 4;
const segWidth = width / segments;
for (let i = 0; i <= segments; i++) {
const px = x + i * segWidth;
const py = y + layer.offset + Math.sin((i / segments) * Math.PI * 2) * layer.amp;
if (i === 0) {
ctx.lineTo(px, py);
} else {
// 计算控制点
const prevX = x + (i - 1) * segWidth;
const ctrlX = (prevX + px) / 2;
const ctrlY = y + layer.offset + Math.sin(((i - 0.5) / segments) * Math.PI * 2) * layer.amp;
ctx.quadraticCurveTo(ctrlX, ctrlY, px, py);
}
}
ctx.lineTo(x + width, y + 100);
ctx.lineTo(x, y + 100);
ctx.closePath();
ctx.fill();
}
ctx.restore();
}
// 绘制花瓣
private drawFlowerPath(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
radius: number,
petals: number
): void {
ctx.save();
ctx.translate(cx, cy);
const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, radius);
gradient.addColorStop(0, '#ffecd2');
gradient.addColorStop(1, '#fcb69f');
ctx.fillStyle = gradient;
// 绘制多个花瓣
for (let i = 0; i < petals; i++) {
ctx.save();
ctx.rotate((i / petals) * Math.PI * 2);
ctx.beginPath();
ctx.moveTo(0, 0);
// 使用贝塞尔曲线绘制花瓣形状
ctx.bezierCurveTo(
radius * 0.3, -radius * 0.5,
radius * 0.8, -radius * 0.3,
0, -radius
);
ctx.bezierCurveTo(
-radius * 0.8, -radius * 0.3,
-radius * 0.3, -radius * 0.5,
0, 0
);
ctx.fill();
ctx.restore();
}
// 绘制花心
ctx.fillStyle = '#ffd93d';
ctx.beginPath();
ctx.arc(0, 0, radius * 0.25, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// 绘制螺旋
private drawSpiralPath(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
turns: number,
maxRadius: number
): void {
ctx.save();
ctx.translate(cx, cy);
const gradient = ctx.createConicGradient(0, 0, 0);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(0.25, '#764ba2');
gradient.addColorStop(0.5, '#f093fb');
gradient.addColorStop(0.75, '#f5576c');
gradient.addColorStop(1, '#667eea');
ctx.strokeStyle = gradient;
ctx.lineWidth = 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(0, 0);
// 阿基米德螺旋
const steps = turns * 100;
for (let i = 0; i <= steps; i++) {
const t = i / steps;
const angle = t * turns * Math.PI * 2;
const radius = t * maxRadius;
const x = radius * Math.cos(angle);
const y = radius * Math.sin(angle);
ctx.lineTo(x, y);
}
ctx.stroke();
ctx.restore();
}
}
3.3 着色器与渐变效果
// 文件:ShaderDrawing.ets
import { Canvas, Paint, Color, Shader, Path } from '@ohos.graphics';
@Entry
@Component
struct ShaderDrawing {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
@State private animationProgress: number = 0;
build() {
Column() {
Canvas(this.context)
.width('100%')
.height(500)
.onReady(() => {
this.drawShaders();
})
Row({ space: 20 }) {
Button('播放动画')
.onClick(() => this.playAnimation())
}
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#0f0f23')
}
private drawShaders(): void {
const ctx = this.context;
ctx.clearRect(0, 0, ctx.width, ctx.height);
// ========== 线性渐变 ==========
this.drawLinearGradient(ctx, 50, 50, 180, 100);
// ========== 径向渐变 ==========
this.drawRadialGradient(ctx, 320, 100, 60);
// ========== 扫描渐变(圆锥渐变)==========
this.drawSweepGradient(ctx, 500, 100, 60);
// ========== 多色渐变 ==========
this.drawMultiColorGradient(ctx, 50, 200, 250, 80);
// ========== 渐变描边 ==========
this.drawGradientStroke(ctx, 350, 240, 100);
// ========== 动态渐变 ==========
this.drawAnimatedGradient(ctx, 50, 350, 250, 120);
// ========== 位图着色器 ==========
this.drawPatternShader(ctx, 350, 350, 200, 120);
}
// 线性渐变
private drawLinearGradient(
ctx: CanvasRenderingContext2D,
x: number, y: number,
width: number, height: number
): void {
const gradient = ctx.createLinearGradient(x, y, x + width, y + height);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.fillStyle = gradient;
ctx.fillRect(x, y, width, height);
// 标签
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('线性渐变', x, y - 10);
}
// 径向渐变
private drawRadialGradient(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
radius: number
): void {
const gradient = ctx.createRadialGradient(
cx - radius * 0.3, cy - radius * 0.3, 0, // 内圆
cx, cy, radius // 外圆
);
gradient.addColorStop(0, '#ffffff');
gradient.addColorStop(0.5, '#f093fb');
gradient.addColorStop(1, '#f5576c');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('径向渐变', cx - 30, cy - radius - 10);
}
// 扫描渐变
private drawSweepGradient(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
radius: number
): void {
const gradient = ctx.createConicGradient(0, cx, cy);
gradient.addColorStop(0, '#ff6b6b');
gradient.addColorStop(0.25, '#feca57');
gradient.addColorStop(0.5, '#48dbfb');
gradient.addColorStop(0.75, '#ff9ff3');
gradient.addColorStop(1, '#ff6b6b');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('扫描渐变', cx - 30, cy - radius - 10);
}
// 多色渐变
private drawMultiColorGradient(
ctx: CanvasRenderingContext2D,
x: number, y: number,
width: number, height: number
): void {
const gradient = ctx.createLinearGradient(x, y, x + width, y);
const colors = [
'#ff6b6b', '#feca57', '#48dbfb',
'#ff9ff3', '#54a0ff', '#5f27cd'
];
colors.forEach((color, index) => {
gradient.addColorStop(index / (colors.length - 1), color);
});
ctx.fillStyle = gradient;
ctx.fillRect(x, y, width, height);
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('多色渐变', x, y - 10);
}
// 渐变描边
private drawGradientStroke(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
radius: number
): void {
const gradient = ctx.createLinearGradient(
cx - radius, cy - radius,
cx + radius, cy + radius
);
gradient.addColorStop(0, '#00d2ff');
gradient.addColorStop(0.5, '#3a7bd5');
gradient.addColorStop(1, '#00d2ff');
ctx.strokeStyle = gradient;
ctx.lineWidth = 8;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.arc(cx, cy, radius, -Math.PI / 2, Math.PI * 1.5);
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('渐变描边', cx - 30, cy - radius - 20);
}
// 动态渐变
private drawAnimatedGradient(
ctx: CanvasRenderingContext2D,
x: number, y: number,
width: number, height: number
): void {
const progress = this.animationProgress;
const offset = progress % 1;
const gradient = ctx.createLinearGradient(x, y, x + width, y);
gradient.addColorStop((0 + offset) % 1, '#667eea');
gradient.addColorStop((0.33 + offset) % 1, '#764ba2');
gradient.addColorStop((0.66 + offset) % 1, '#f093fb');
gradient.addColorStop(1, '#667eea');
// 圆角矩形
ctx.fillStyle = gradient;
ctx.beginPath();
const radius = 16;
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('动态渐变', x, y - 10);
}
// 位图着色器(图案填充)
private drawPatternShader(
ctx: CanvasRenderingContext2D,
x: number, y: number,
width: number, height: number
): void {
// 创建图案画布
const patternCanvas = new OffscreenCanvas(20, 20);
const patternCtx = patternCanvas.getContext('2d');
// 绘制图案
patternCtx.fillStyle = '#1a1a2e';
patternCtx.fillRect(0, 0, 20, 20);
patternCtx.fillStyle = '#667eea';
patternCtx.beginPath();
patternCtx.arc(10, 10, 5, 0, Math.PI * 2);
patternCtx.fill();
// 创建图案
const pattern = ctx.createPattern(patternCanvas, 'repeat');
ctx.fillStyle = pattern;
// 圆角矩形裁剪
ctx.save();
ctx.beginPath();
const radius = 16;
ctx.moveTo(x + radius, y);
ctx.arcTo(x + width, y, x + width, y + height, radius);
ctx.arcTo(x + width, y + height, x, y + height, radius);
ctx.arcTo(x, y + height, x, y, radius);
ctx.arcTo(x, y, x + width, y, radius);
ctx.closePath();
ctx.clip();
ctx.fillRect(x, y, width, height);
ctx.restore();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('图案填充', x, y - 10);
}
// 播放动画
private playAnimation(): void {
const animate = () => {
this.animationProgress += 0.01;
this.drawShaders();
requestAnimationFrame(animate);
};
animate();
}
}
3.4 滤镜与特效处理
// 文件:FilterEffectDrawing.ets
import { Canvas, Paint, Color, Path, MaskFilter, ColorFilter } from '@ohos.graphics';
@Entry
@Component
struct FilterEffectDrawing {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Column() {
Canvas(this.context)
.width('100%')
.height('100%')
.onReady(() => {
this.drawFilterEffects();
})
}
.width('100%')
.height('100%')
}
private drawFilterEffects(): void {
const ctx = this.context;
ctx.clearRect(0, 0, ctx.width, ctx.height);
// 背景
ctx.fillStyle = '#0f0f23';
ctx.fillRect(0, 0, ctx.width, ctx.height);
// ========== 阴影效果 ==========
this.drawWithShadow(ctx, 100, 100, 80);
// ========== 模糊效果 ==========
this.drawWithBlur(ctx, 300, 100, 60);
// ========== 发光效果 ==========
this.drawWithGlow(ctx, 500, 100, 50);
// ========== 混合模式 ==========
this.drawBlendModes(ctx, 100, 250);
// ========== 透明度效果 ==========
this.drawAlphaEffect(ctx, 400, 250);
// ========== 颜色滤镜 ==========
this.drawColorFilter(ctx, 100, 400);
}
// 阴影效果
private drawWithShadow(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
size: number
): void {
ctx.save();
// 设置阴影
ctx.shadowColor = 'rgba(102, 126, 234, 0.8)';
ctx.shadowBlur = 20;
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
// 绘制矩形
const gradient = ctx.createLinearGradient(cx - size/2, cy - size/2, cx + size/2, cy + size/2);
gradient.addColorStop(0, '#667eea');
gradient.addColorStop(1, '#764ba2');
ctx.fillStyle = gradient;
ctx.fillRect(cx - size/2, cy - size/2, size, size);
ctx.restore();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('阴影效果', cx - 30, cy + size/2 + 25);
}
// 模糊效果
private drawWithBlur(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
radius: number
): void {
ctx.save();
// 模糊滤镜(通过阴影模拟)
ctx.shadowColor = 'rgba(240, 147, 251, 0.6)';
ctx.shadowBlur = 15;
ctx.fillStyle = '#f093fb';
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('模糊效果', cx - 30, cy + radius + 25);
}
// 发光效果
private drawWithGlow(
ctx: CanvasRenderingContext2D,
cx: number, cy: number,
radius: number
): void {
ctx.save();
// 多层阴影模拟发光
const layers = [
{ blur: 30, alpha: 0.1 },
{ blur: 20, alpha: 0.3 },
{ blur: 10, alpha: 0.5 }
];
for (const layer of layers) {
ctx.shadowColor = `rgba(0, 242, 254, ${layer.alpha})`;
ctx.shadowBlur = layer.blur;
ctx.fillStyle = '#00f2fe';
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.fill();
}
// 内部实心
ctx.shadowColor = 'transparent';
ctx.fillStyle = '#00f2fe';
ctx.beginPath();
ctx.arc(cx, cy, radius * 0.8, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('发光效果', cx - 30, cy + radius + 25);
}
// 混合模式
private drawBlendModes(
ctx: CanvasRenderingContext2D,
x: number, y: number
): void {
const modes = [
{ name: 'normal', mode: 'source-over' },
{ name: 'multiply', mode: 'multiply' },
{ name: 'screen', mode: 'screen' },
{ name: 'overlay', mode: 'overlay' }
];
const size = 50;
const gap = 10;
modes.forEach((item, index) => {
const px = x + index * (size + gap);
// 底层圆
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(px + size/4, y + size/2, size/3, 0, Math.PI * 2);
ctx.fill();
// 顶层圆(应用混合模式)
ctx.globalCompositeOperation = item.mode;
ctx.fillStyle = '#48dbfb';
ctx.beginPath();
ctx.arc(px + size*3/4, y + size/2, size/3, 0, Math.PI * 2);
ctx.fill();
ctx.globalCompositeOperation = 'source-over';
// 标签
ctx.fillStyle = '#ffffff';
ctx.font = '10px sans-serif';
ctx.fillText(item.name, px, y + size + 15);
});
}
// 透明度效果
private drawAlphaEffect(
ctx: CanvasRenderingContext2D,
x: number, y: number
): void {
const alphas = [1.0, 0.8, 0.6, 0.4, 0.2];
const size = 40;
alphas.forEach((alpha, index) => {
ctx.globalAlpha = alpha;
ctx.fillStyle = '#f093fb';
ctx.fillRect(x + index * (size + 10), y, size, size);
});
ctx.globalAlpha = 1.0;
ctx.fillStyle = '#ffffff';
ctx.font = '14px sans-serif';
ctx.fillText('透明度渐变', x, y + size + 25);
}
// 颜色滤镜
private drawColorFilter(
ctx: CanvasRenderingContext2D,
x: number, y: number
): void {
const filters = [
{ name: '原图', filter: 'none' },
{ name: '灰度', filter: 'grayscale(100%)' },
{ name: '反色', filter: 'invert(100%)' },
{ name: '褐色', filter: 'sepia(100%)' }
];
const size = 60;
filters.forEach((item, index) => {
const px = x + index * (size + 20);
ctx.save();
if (item.filter !== 'none') {
ctx.filter = item.filter;
}
// 绘制彩色渐变矩形
const gradient = ctx.createLinearGradient(px, y, px + size, y + size);
gradient.addColorStop(0, '#ff6b6b');
gradient.addColorStop(0.5, '#48dbfb');
gradient.addColorStop(1, '#feca57');
ctx.fillStyle = gradient;
ctx.fillRect(px, y, size, size);
ctx.restore();
// 标签
ctx.fillStyle = '#ffffff';
ctx.font = '12px sans-serif';
ctx.fillText(item.name, px + 10, y + size + 15);
});
}
}
四、踩坑与注意事项
4.1 常见问题与解决方案
classDef issueNode fill:#ffebee,stroke:#c62828,stroke-width:2px
classDef fixNode fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
flowchart TB
Issue1[问题:渐变颜色不正确]:::issueNode
Issue2[问题:路径绘制不闭合]:::issueNode
Issue3[问题:变换影响后续绘制]:::issueNode
Issue4[问题:裁剪区域无法恢复]:::issueNode
Fix1[解决:检查addColorStop位置<br/>颜色格式使用标准格式]:::fixNode
Fix2[解决:调用closePath闭合路径<br/>或使用lineTo连接起点]:::fixNode
Fix3[解决:使用save/restore配对<br/>或在变换后逆变换]:::fixNode
Fix4[解决:save/restore管理状态<br/>避免嵌套裁剪]:::fixNode
Issue1 --> Fix1
Issue2 --> Fix2
Issue3 --> Fix3
Issue4 --> Fix4
4.2 关键注意事项
1. 状态管理
// ❌ 错误:变换影响后续绘制
ctx.translate(100, 100);
ctx.fillRect(0, 0, 50, 50);
// 后续绘制也受变换影响
ctx.fillRect(200, 200, 50, 50); // 位置错误
// ✅ 正确:使用save/restore
ctx.save();
ctx.translate(100, 100);
ctx.fillRect(0, 0, 50, 50);
ctx.restore();
// 后续绘制不受影响
ctx.fillRect(200, 200, 50, 50);
2. 路径绘制
// ❌ 错误:忘记闭合路径
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 0);
ctx.lineTo(50, 100);
// 缺少closePath,三角形不闭合
ctx.fill();
// ✅ 正确:闭合路径
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 0);
ctx.lineTo(50, 100);
ctx.closePath(); // 闭合路径
ctx.fill();
3. 性能优化
// ❌ 错误:每帧创建新对象
function onFrame() {
const paint = new Paint(); // 每帧创建
paint.setColor(Color.RED);
canvas.drawRect(0, 0, 100, 100, paint);
}
// ✅ 正确:复用对象
const paint = new Paint(); // 创建一次
paint.setColor(Color.RED);
function onFrame() {
canvas.drawRect(0, 0, 100, 100, paint);
}
五、总结
5.1 Drawing API核心能力
| 能力类别 | 核心API | 应用场景 |
|---|---|---|
| 基础绘制 | drawRect, drawCircle, drawPath | 图形、图表、自定义UI |
| 路径操作 | Path, bezierCurveTo, arcTo | 复杂图形、矢量图标 |
| 着色效果 | Shader, Gradient | 背景、装饰、视觉增强 |
| 变换操作 | translate, rotate, scale | 动画、布局适配 |
| 特效处理 | shadow, filter, blend | 阴影、模糊、混合效果 |
5.2 最佳实践总结
- 对象复用:Paint、Path等对象应复用,避免频繁创建
- 状态管理:使用save/restore管理Canvas状态
- 批量绘制:合并相同属性的绘制操作
- 硬件加速:开启抗锯齿,利用GPU加速
- 脏区域更新:只重绘变化区域,减少绘制开销
Drawing API为HarmonyOS应用提供了强大的2D图形绘制能力,通过掌握其核心原理和最佳实践,开发者可以构建出视觉效果丰富、性能优异的应用界面。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)