HarmonyOS图形处理:Canvas绘制与动画开发实战

发布于:2025-09-15 ⋅ 阅读:(25) ⋅ 点赞:(0)

本文将全面介绍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. 最佳实践与性能建议

  1. 减少重绘区域:使用clearRect()只清除需要更新的区域,而不是整个画布
  2. 使用离屏Canvas:对静态内容或复杂图案进行预渲染
  3. 避免频繁的样式更改:批量绘制相同样式的图形
  4. 使用requestAnimationFrame:实现平滑的动画效果
  5. 优化路径绘制:使用beginPath()closePath()管理路径状态
  6. 合理使用透明度:过多的透明度计算会增加性能开销
  7. 分层渲染:将静态内容和动态内容分离到不同的Canvas层

通过掌握这些Canvas绘制和动画开发技巧,您将能够在HarmonyOS应用中创建丰富多样的图形界面和流畅的交互体验。记得在实际开发中根据具体需求选择合适的优化策略,平衡视觉效果和性能表现。

需要参加鸿蒙认证的请点击 鸿蒙认证链接