本文将全面介绍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应用中创建丰富多样的图形界面和流畅的交互体验。记得在实际开发中根据具体需求选择合适的优化策略,平衡视觉效果和性能表现。