本文将全面介绍HarmonyOS 5中Canvas组件的使用方法和动画开发技巧,通过详细的代码示例和最佳实践,帮助您掌握图形绘制和动态效果实现的核心技能。
1. Canvas组件基础与核心API
Canvas是HarmonyOS中用于2D图形绘制的重要组件,提供了丰富的绘图接口和灵活的动画支持。
1.1 Canvas基本用法
import { CanvasRenderingContext2D } from '@ohos.graphics.canvas';
@Entry
@Component
struct BasicCanvasDemo {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Column() {
// 创建Canvas组件
Canvas(this.ctx)
.width('100%')
.height(300)
.backgroundColor('#f0f0f0')
.onReady(() => {
this.drawBasicShapes();
})
}
.padding(12)
}
// 绘制基本图形
private drawBasicShapes() {
// 绘制矩形
this.ctx.fillStyle = '#3498db';
this.ctx.fillRect(50, 50, 100, 80);
// 绘制圆形
this.ctx.beginPath();
this.ctx.arc(250, 90, 40, 0, Math.PI * 2);
this.ctx.fillStyle = '#e74c3c';
this.ctx.fill();
// 绘制文本
this.ctx.font = '16px sans-serif';
this.ctx.fillStyle = '#2c3e50';
this.ctx.fillText('HarmonyOS Canvas', 120, 180);
// 绘制线条
this.ctx.beginPath();
this.ctx.moveTo(50, 220);
this.ctx.lineTo(300, 220);
this.ctx.strokeStyle = '#27ae60';
this.ctx.lineWidth = 3;
this.ctx.stroke();
}
}
1.2 核心绘图API详解
HarmonyOS Canvas提供了完整的2D绘图API,主要包含以下几类方法:
- 路径绘制:
beginPath()
,moveTo()
,lineTo()
,arc()
,rect()
,closePath()
- 样式设置:
fillStyle
,strokeStyle
,lineWidth
,lineCap
,lineJoin
- 填充与描边:
fill()
,stroke()
,fillRect()
,strokeRect()
- 文本绘制:
fillText()
,strokeText()
,font
,textAlign
- 变换操作:
translate()
,rotate()
,scale()
,transform()
,setTransform()
- 图像绘制:
drawImage()
,createImageData()
,putImageData()
2. 高级绘图技巧
2.1 复杂路径与贝塞尔曲线
@Component
struct AdvancedPathDemo {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Canvas(this.ctx)
.width('100%')
.height(400)
.onReady(() => {
this.drawComplexPaths();
})
}
private drawComplexPaths() {
// 绘制二次贝塞尔曲线
this.ctx.beginPath();
this.ctx.moveTo(50, 200);
this.ctx.quadraticCurveTo(150, 50, 250, 200);
this.ctx.strokeStyle = '#8e44ad';
this.ctx.lineWidth = 4;
this.ctx.stroke();
// 绘制三次贝塞尔曲线
this.ctx.beginPath();
this.ctx.moveTo(50, 250);
this.ctx.bezierCurveTo(100, 150, 200, 350, 250, 250);
this.ctx.strokeStyle = '#f39c12';
this.ctx.lineWidth = 4;
this.ctx.stroke();
// 绘制复杂形状
this.ctx.beginPath();
this.ctx.moveTo(300, 50);
this.ctx.lineTo(350, 150);
this.ctx.arcTo(400, 200, 350, 250, 50);
this.ctx.lineTo(300, 200);
this.ctx.closePath();
this.ctx.fillStyle = 'rgba(52, 152, 219, 0.7)';
this.ctx.fill();
}
}
2.2 渐变与阴影效果
@Component
struct GradientShadowDemo {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
build() {
Canvas(this.ctx)
.width('100%')
.height(300)
.onReady(() => {
this.drawGradientEffects();
})
}
private drawGradientEffects() {
// 创建线性渐变
const linearGradient = this.ctx.createLinearGradient(0, 0, 300, 0);
linearGradient.addColorStop(0, '#ff9a9e');
linearGradient.addColorStop(1, '#fad0c4');
this.ctx.fillStyle = linearGradient;
this.ctx.fillRect(50, 50, 100, 100);
// 创建径向渐变
const radialGradient = this.ctx.createRadialGradient(250, 100, 10, 250, 100, 60);
radialGradient.addColorStop(0, '#a1c4fd');
radialGradient.addColorStop(1, '#c2e9fb');
this.ctx.fillStyle = radialGradient;
this.ctx.beginPath();
this.ctx.arc(250, 100, 60, 0, Math.PI * 2);
this.ctx.fill();
// 添加阴影效果
this.ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
this.ctx.shadowBlur = 15;
this.ctx.shadowOffsetX = 10;
this.ctx.shadowOffsetY = 10;
this.ctx.fillStyle = '#27ae60';
this.ctx.fillRect(150, 180, 100, 80);
// 重置阴影
this.ctx.shadowColor = 'transparent';
}
}
3. 动画开发实战
3.1 基础动画实现
@Entry
@Component
struct BasicAnimationDemo {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
@State private angle: number = 0;
@State private position: number = 50;
private animationId: number = 0;
build() {
Column() {
Canvas(this.ctx)
.width('100%')
.height(300)
.onReady(() => {
this.startAnimation();
})
.onDisappear(() => {
this.stopAnimation();
})
Button('重置动画')
.onClick(() => {
this.resetAnimation();
})
.margin(10)
.width(200)
}
}
private startAnimation() {
const animate = () => {
this.ctx.clearRect(0, 0, 400, 300);
// 更新动画状态
this.angle = (this.angle + 2) % 360;
this.position = 50 + Math.sin(Date.now() / 500) * 100;
// 绘制旋转矩形
this.ctx.save();
this.ctx.translate(150, 150);
this.ctx.rotate(this.angle * Math.PI / 180);
this.ctx.fillStyle = '#3498db';
this.ctx.fillRect(-40, -40, 80, 80);
this.ctx.restore();
// 绘制弹跳球
this.ctx.beginPath();
this.ctx.arc(this.position, 250, 20, 0, Math.PI * 2);
this.ctx.fillStyle = '#e74c3c';
this.ctx.fill();
this.animationId = requestAnimationFrame(animate);
};
animate();
}
private stopAnimation() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
private resetAnimation() {
this.stopAnimation();
this.angle = 0;
this.position = 50;
this.startAnimation();
}
}
3.2 高级动画:粒子系统
class Particle {
x: number;
y: number;
vx: number;
vy: number;
radius: number;
color: string;
alpha: number;
constructor(width: number, height: number) {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.vx = (Math.random() - 0.5) * 2;
this.vy = (Math.random() - 0.5) * 2;
this.radius = Math.random() * 5 + 1;
this.color = `hsl(${Math.random() * 360}, 50%, 50%)`;
this.alpha = Math.random() * 0.5 + 0.5;
}
update(width: number, height: number) {
this.x += this.vx;
this.y += this.vy;
// 边界检测
if (this.x < 0 || this.x > width) this.vx *= -1;
if (this.y < 0 || this.y > height) this.vy *= -1;
// 透明度衰减
this.alpha -= 0.005;
if (this.alpha <= 0) {
this.alpha = 0;
}
}
}
@Entry
@Component
struct ParticleSystemDemo {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
private particles: Particle[] = [];
private animationId: number = 0;
private width: number = 400;
private height: number = 400;
build() {
Canvas(this.ctx)
.width('100%')
.height(this.height)
.onReady(() => {
this.initializeParticles();
this.startAnimation();
})
.onDisappear(() => {
this.stopAnimation();
})
}
private initializeParticles() {
for (let i = 0; i < 100; i++) {
this.particles.push(new Particle(this.width, this.height));
}
}
private startAnimation() {
const animate = () => {
// 清空画布
this.ctx.clearRect(0, 0, this.width, this.height);
// 更新并绘制粒子
this.particles.forEach((particle, index) => {
particle.update(this.width, this.height);
// 移除消失的粒子并添加新粒子
if (particle.alpha <= 0) {
this.particles.splice(index, 1);
this.particles.push(new Particle(this.width, this.height));
}
// 绘制粒子
this.ctx.beginPath();
this.ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
this.ctx.fillStyle = particle.color;
this.ctx.globalAlpha = particle.alpha;
this.ctx.fill();
});
// 重置透明度
this.ctx.globalAlpha = 1;
this.animationId = requestAnimationFrame(animate);
};
animate();
}
private stopAnimation() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
}
4. 性能优化技巧
4.1 离屏Canvas与缓存
@Component
struct OffscreenCanvasDemo {
private mainSettings: RenderingContextSettings = new RenderingContextSettings(true);
private mainCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.mainSettings);
private offscreenSettings: RenderingContextSettings = new RenderingContextSettings(true);
private offscreenCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.offscreenSettings);
private complexPattern: ImageBitmap | null = null;
build() {
Canvas(this.mainCtx)
.width('100%')
.height(300)
.onReady(async () => {
await this.createOffscreenPattern();
this.drawUsingCache();
})
}
private async createOffscreenPattern() {
// 在离屏Canvas上绘制复杂图案
this.offscreenCtx.fillStyle = '#34495e';
this.offscreenCtx.fillRect(0, 0, 100, 100);
for (let i = 0; i < 20; i++) {
this.offscreenCtx.beginPath();
this.offscreenCtx.arc(
Math.random() * 100,
Math.random() * 100,
Math.random() * 5 + 1,
0,
Math.PI * 2
);
this.offscreenCtx.fillStyle = `hsl(${Math.random() * 360}, 70%, 60%)`;
this.offscreenCtx.fill();
}
// 转换为ImageBitmap用于高效重绘
this.complexPattern = await createImageBitmap(this.offscreenCtx.canvas);
}
private drawUsingCache() {
if (!this.complexPattern) return;
// 使用缓存图案进行绘制(性能优化)
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 3; j++) {
this.mainCtx.drawImage(
this.complexPattern,
i * 110 + 20,
j * 110 + 20,
100,
100
);
}
}
}
}
4.2 分层渲染与脏矩形优化
@Component
struct LayeredRenderingDemo {
private backgroundSettings: RenderingContextSettings = new RenderingContextSettings(true);
private backgroundCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.backgroundSettings);
private foregroundSettings: RenderingContextSettings = new RenderingContextSettings(true);
private foregroundCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.foregroundSettings);
@State private mouseX: number = 0;
@State private mouseY: number = 0;
build() {
Stack() {
// 背景层(静态内容,只需绘制一次)
Canvas(this.backgroundCtx)
.width('100%')
.height(400)
.onReady(() => {
this.drawBackground();
})
// 前景层(动态内容,频繁更新)
Canvas(this.foregroundCtx)
.width('100%')
.height(400)
.onReady(() => {
this.startInteraction();
})
.onTouchMove((event) => {
this.handleTouchMove(event);
})
}
}
private drawBackground() {
// 绘制静态背景
const gradient = this.backgroundCtx.createLinearGradient(0, 0, 400, 400);
gradient.addColorStop(0, '#1a2980');
gradient.addColorStop(1, '#26d0ce');
this.backgroundCtx.fillStyle = gradient;
this.backgroundCtx.fillRect(0, 0, 400, 400);
// 绘制网格
this.backgroundCtx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
this.backgroundCtx.lineWidth = 1;
for (let i = 0; i < 400; i += 20) {
this.backgroundCtx.beginPath();
this.backgroundCtx.moveTo(i, 0);
this.backgroundCtx.lineTo(i, 400);
this.backgroundCtx.stroke();
this.backgroundCtx.beginPath();
this.backgroundCtx.moveTo(0, i);
this.backgroundCtx.lineTo(400, i);
this.backgroundCtx.stroke();
}
}
private handleTouchMove(event: TouchEvent) {
const touch = event.touches[0];
if (touch) {
this.mouseX = touch.x;
this.mouseY = touch.y;
this.updateForeground();
}
}
private updateForeground() {
// 只清除需要更新的区域(脏矩形优化)
this.foregroundCtx.clearRect(0, 0, 400, 400);
// 绘制交互效果
this.foregroundCtx.beginPath();
this.foregroundCtx.arc(this.mouseX, this.mouseY, 50, 0, Math.PI * 2);
this.foregroundCtx.fillStyle = 'rgba(255, 255, 255, 0.2)';
this.foregroundCtx.fill();
this.foregroundCtx.beginPath();
this.foregroundCtx.arc(this.mouseX, this.mouseY, 20, 0, Math.PI * 2);
this.foregroundCtx.fillStyle = 'rgba(255, 255, 255, 0.5)';
this.foregroundCtx.fill();
}
private startInteraction() {
// 初始绘制
this.updateForeground();
}
}
5. 实战案例:数据可视化图表
interface ChartData {
label: string;
value: number;
color: string;
}
@Entry
@Component
struct DataChartDemo {
private settings: RenderingContextSettings = new RenderingContextSettings(true);
private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);
@State private chartData: ChartData[] = [
{ label: 'Q1', value: 120, color: '#3498db' },
{ label: 'Q2', value: 180, color: '#2ecc71' },
{ label: 'Q3', value: 90, color: '#e74c3c' },
{ label: 'Q4', value: 210, color: '#f39c12' }
];
build() {
Column() {
Canvas(this.ctx)
.width('100%')
.height(400)
.onReady(() => {
this.drawBarChart();
})
Button('更新数据')
.onClick(() => {
this.updateData();
})
.margin(10)
.width(200)
}
}
private drawBarChart() {
const padding = 40;
const chartWidth = 400 - padding * 2;
const chartHeight = 300 - padding * 2;
const barWidth = chartWidth / this.chartData.length * 0.6;
const maxValue = Math.max(...this.chartData.map(item => item.value));
// 清空画布
this.ctx.clearRect(0, 0, 400, 400);
// 绘制坐标轴
this.ctx.strokeStyle = '#7f8c8d';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(padding, padding);
this.ctx.lineTo(padding, 400 - padding);
this.ctx.lineTo(400 - padding, 400 - padding);
this.ctx.stroke();
// 绘制刻度
this.ctx.textAlign = 'right';
this.ctx.font = '12px sans-serif';
this.ctx.fillStyle = '#7f8c8d';
for (let i = 0; i <= 5; i++) {
const value = (maxValue / 5) * i;
const y = 400 - padding - (value / maxValue) * chartHeight;
this.ctx.beginPath();
this.ctx.moveTo(padding - 5, y);
this.ctx.lineTo(padding, y);
this.ctx.stroke();
this.ctx.fillText(value.toString(), padding - 10, y + 4);
}
// 绘制柱状图
this.chartData.forEach((item, index) => {
const barHeight = (item.value / maxValue) * chartHeight;
const x = padding + index * (chartWidth / this.chartData.length) +
(chartWidth / this.chartData.length - barWidth) / 2;
const y = 400 - padding - barHeight;
// 绘制柱子
this.ctx.fillStyle = item.color;
this.ctx.fillRect(x, y, barWidth, barHeight);
// 绘制数值
this.ctx.textAlign = 'center';
this.ctx.fillStyle = '#2c3e50';
this.ctx.fillText(item.value.toString(), x + barWidth / 2, y - 5);
// 绘制标签
this.ctx.fillText(item.label, x + barWidth / 2, 400 - padding + 20);
});
// 绘制标题
this.ctx.textAlign = 'center';
this.ctx.font = '16px sans-serif';
this.ctx.fillStyle = '#2c3e50';
this.ctx.fillText('季度销售数据', 200, 30);
}
private updateData() {
// 随机更新数据
this.chartData = this.chartData.map(item => ({
...item,
value: Math.floor(Math.random() * 200) + 50
}));
this.drawBarChart();
}
}
6. 最佳实践与性能建议
- 减少重绘区域:使用
clearRect()
只清除需要更新的区域,而不是整个画布 - 使用离屏Canvas:对静态内容或复杂图案进行预渲染
- 避免频繁的样式更改:批量绘制相同样式的图形
- 使用requestAnimationFrame:实现平滑的动画效果
- 优化路径绘制:使用
beginPath()
和closePath()
管理路径状态 - 合理使用透明度:过多的透明度计算会增加性能开销
- 分层渲染:将静态内容和动态内容分离到不同的Canvas层
通过掌握这些Canvas绘制和动画开发技巧,您将能够在HarmonyOS应用中创建丰富多样的图形界面和流畅的交互体验。记得在实际开发中根据具体需求选择合适的优化策略,平衡视觉效果和性能表现。
需要参加鸿蒙认证的请点击 鸿蒙认证链接