HarmonyOS APP开发:Drawing绘图API与图形操作

举报
Jack20 发表于 2026/06/22 20:55:43 2026/06/22
【摘要】 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 最佳实践总结

  1. 对象复用:Paint、Path等对象应复用,避免频繁创建
  2. 状态管理:使用save/restore管理Canvas状态
  3. 批量绘制:合并相同属性的绘制操作
  4. 硬件加速:开启抗锯齿,利用GPU加速
  5. 脏区域更新:只重绘变化区域,减少绘制开销

Drawing API为HarmonyOS应用提供了强大的2D图形绘制能力,通过掌握其核心原理和最佳实践,开发者可以构建出视觉效果丰富、性能优异的应用界面。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。