HarmonyOS文字书写功能实现指南

目录

  1. 功能概述
  2. 项目准备
  3. 第一步:创建数据模型
  4. 第二步:实现服务层
  5. 第三步:创建工具栏组件
  6. 第四步:实现绘图画布页面
  7. 第五步:配置路由
  8. 第六步:添加入口导航
  9. 功能测试
  10. 扩展功能

功能概述

文字书写功能包含两个核心模块:

1. 简笔画工坊(DrawingWorkshop)

  • Canvas 触摸绘图
  • 铅笔和橡皮工具
  • 8种颜色选择
  • 5种画笔粗细
  • 撤销/重做功能
  • 清空画布
  • 作品保存

2. 数字描红练习(NumberTracing)

  • 数字模板显示
  • 触摸描红进度追踪
  • 完成奖励系统

本指南主要实现简笔画工坊功能,这是文字书写的核心能力。


项目准备

2.1 创建目录结构

在 MyApplication 项目中创建以下目录结构:

复制代码
entry/src/main/ets/
├── pages/
│   └── DrawingWorkshopPage.ets    (新建)
├── models/
│   └── DrawingModels.ets          (新建)
├── services/
│   └── DrawingService.ets         (新建)
└── components/
    └── DrawingToolbar.ets         (新建)

在 DevEco Studio 中,右键点击相应目录,选择 New > Directory 创建文件夹。


第一步:创建数据模型

1.1 创建 DrawingModels.ets

entry/src/main/ets/models/ 目录下创建 DrawingModels.ets 文件:

typescript 复制代码
/**
 * 绘图工具枚举
 */
export enum DrawingTool {
  PENCIL = 'pencil',
  ERASER = 'eraser'
}

/**
 * 绘图点接口
 */
export interface DrawingPoint {
  x: number;
  y: number;
}

/**
 * 绘图路径接口
 */
export interface DrawingPath {
  tool: DrawingTool;
  points: DrawingPoint[];
  color: string;
  width: number;
}

/**
 * 颜色选项接口
 */
export interface ColorOption {
  name: string;
  color: string;
}

/**
 * 绘画教程步骤接口
 */
export interface DrawingStep {
  path: string;
  description: string;
}

/**
 * 绘画教程接口
 */
export interface DrawingTutorial {
  id: string;
  name: string;
  difficulty: 'easy' | 'medium' | 'hard';
  category: string;
  steps: DrawingStep[];
  preview: string;
}

/**
 * 预设颜色选项
 */
export const COLOR_OPTIONS: ColorOption[] = [
  { name: '黑色', color: '#333333' },
  { name: '红色', color: '#EF5350' },
  { name: '橙色', color: '#FF9B71' },
  { name: '黄色', color: '#FFD700' },
  { name: '绿色', color: '#52C41A' },
  { name: '蓝色', color: '#7DD3FC' },
  { name: '紫色', color: '#B19CD9' },
  { name: '粉色', color: '#FF6B9D' }
];

/**
 * 画笔粗细选项
 */
export const BRUSH_SIZES: number[] = [2, 4, 6, 8, 10];

/**
 * 绘画教程数据
 */
export const DRAWING_TUTORIALS: DrawingTutorial[] = [
  {
    id: 'drawing_cat',
    name: '小猫咪',
    difficulty: 'easy',
    category: 'animal',
    steps: [
      { path: 'M 50,50 Q 50,30 70,30 Q 90,30 90,50', description: '画圆圆的头' },
      { path: 'M 70,50 L 70,70 L 60,80 M 70,70 L 80,80', description: '画身体和腿' },
      { path: 'M 30,40 L 45,45 M 110,40 L 95,45', description: '画两只耳朵' }
    ],
    preview: 'drawings/cat.svg'
  },
  {
    id: 'drawing_sun',
    name: '太阳',
    difficulty: 'easy',
    category: 'nature',
    steps: [
      { path: 'M 50,50 m -30,0 a 30,30 0 1,0 60,0 a 30,30 0 1,0 -60,0', description: '画圆形太阳' },
      { path: 'M 50,10 L 50,0 M 50,90 L 50,100', description: '画上下光芒' },
      { path: 'M 10,50 L 0,50 M 90,50 L 100,50', description: '画左右光芒' }
    ],
    preview: 'drawings/sun.svg'
  },
  {
    id: 'drawing_house',
    name: '小房子',
    difficulty: 'medium',
    category: 'building',
    steps: [
      { path: 'M 20,50 L 20,90 L 80,90 L 80,50', description: '画房子主体' },
      { path: 'M 10,50 L 50,20 L 90,50', description: '画屋顶' },
      { path: 'M 40,90 L 40,70 L 55,70 L 55,90', description: '画门' },
      { path: 'M 60,55 L 75,55 L 75,70 L 60,70 Z', description: '画窗户' }
    ],
    preview: 'drawings/house.svg'
  }
];

关键说明:

  • DrawingTool 枚举定义了绘图工具类型
  • DrawingPath 记录每一笔的完整信息
  • COLOR_OPTIONSBRUSH_SIZES 是预设的颜色和画笔大小
  • DRAWING_TUTORIALS 提供简笔画教程数据

第二步:实现服务层

2.1 创建 DrawingService.ets

entry/src/main/ets/services/ 目录下创建 DrawingService.ets 文件:

typescript 复制代码
import { DrawingPath, DrawingTutorial, DRAWING_TUTORIALS } from '../models/DrawingModels';

/**
 * 绘图服务类
 * 管理绘图数据和作品保存
 */
export class DrawingService {
  private static instance: DrawingService;

  private constructor() {}

  /**
   * 获取单例实例
   */
  public static getInstance(): DrawingService {
    if (!DrawingService.instance) {
      DrawingService.instance = new DrawingService();
    }
    return DrawingService.instance;
  }

  /**
   * 获取所有绘画教程
   */
  public getTutorials(): DrawingTutorial[] {
    return DRAWING_TUTORIALS;
  }

  /**
   * 根据难度筛选教程
   */
  public getTutorialsByDifficulty(difficulty: 'easy' | 'medium' | 'hard'): DrawingTutorial[] {
    return DRAWING_TUTORIALS.filter(t => t.difficulty === difficulty);
  }

  /**
   * 根据分类筛选教程
   */
  public getTutorialsByCategory(category: string): DrawingTutorial[] {
    return DRAWING_TUTORIALS.filter(t => t.category === category);
  }

  /**
   * 保存作品(简化版本,实际可扩展为数据库存储)
   */
  public async saveArtwork(
    userId: string,
    title: string,
    paths: DrawingPath[]
  ): Promise<boolean> {
    try {
      // 这里可以扩展为实际的数据库存储
      // 目前仅做日志记录
      console.info(`保存作品: ${title}, 用户: ${userId}, 路径数: ${paths.length}`);
      return true;
    } catch (error) {
      console.error('保存作品失败:', error);
      return false;
    }
  }

  /**
   * 导出作品为数据(可用于分享)
   */
  public exportArtwork(paths: DrawingPath[]): string {
    return JSON.stringify(paths);
  }

  /**
   * 导入作品数据
   */
  public importArtwork(data: string): DrawingPath[] | null {
    try {
      return JSON.parse(data) as DrawingPath[];
    } catch (error) {
      console.error('导入作品失败:', error);
      return null;
    }
  }
}

// 导出单例
export const drawingService = DrawingService.getInstance();

关键说明:

  • 使用单例模式管理绘图服务
  • 提供教程获取、作品保存等功能
  • 可扩展为数据库持久化存储

第三步:创建工具栏组件

3.1 创建 DrawingToolbar.ets

entry/src/main/ets/components/ 目录下创建 DrawingToolbar.ets 文件:

typescript 复制代码
import { DrawingTool, ColorOption, COLOR_OPTIONS, BRUSH_SIZES } from '../models/DrawingModels';

/**
 * 绘图工具栏组件
 */
@Component
export struct DrawingToolbar {
  // 当前工具
  @Link currentTool: DrawingTool;
  // 当前颜色
  @Link currentColor: string;
  // 当前画笔大小
  @Link currentBrushSize: number;
  // 是否可撤销
  @Prop canUndo: boolean = false;
  // 是否可重做
  @Prop canRedo: boolean = false;

  // 回调函数
  onUndo?: () => void;
  onRedo?: () => void;
  onClear?: () => void;
  onSave?: () => void;

  @State private showColorPicker: boolean = false;
  @State private showBrushPicker: boolean = false;

  build() {
    Column() {
      // 主工具栏
      Row() {
        // 铅笔工具
        this.ToolButton({
          icon: '✏️',
          label: '铅笔',
          isActive: this.currentTool === DrawingTool.PENCIL,
          onClick: () => {
            this.currentTool = DrawingTool.PENCIL;
          }
        })

        // 橡皮工具
        this.ToolButton({
          icon: '🧽',
          label: '橡皮',
          isActive: this.currentTool === DrawingTool.ERASER,
          onClick: () => {
            this.currentTool = DrawingTool.ERASER;
          }
        })

        // 分隔线
        Divider()
          .vertical(true)
          .height(30)
          .margin({ left: 8, right: 8 })

        // 颜色选择
        this.ToolButton({
          icon: '🎨',
          label: '颜色',
          isActive: this.showColorPicker,
          onClick: () => {
            this.showColorPicker = !this.showColorPicker;
            this.showBrushPicker = false;
          }
        })

        // 画笔大小
        this.ToolButton({
          icon: '⚪',
          label: '大小',
          isActive: this.showBrushPicker,
          onClick: () => {
            this.showBrushPicker = !this.showBrushPicker;
            this.showColorPicker = false;
          }
        })

        // 分隔线
        Divider()
          .vertical(true)
          .height(30)
          .margin({ left: 8, right: 8 })

        // 撤销
        this.ToolButton({
          icon: '↩️',
          label: '撤销',
          isActive: false,
          isDisabled: !this.canUndo,
          onClick: () => {
            this.onUndo?.();
          }
        })

        // 重做
        this.ToolButton({
          icon: '↪️',
          label: '重做',
          isActive: false,
          isDisabled: !this.canRedo,
          onClick: () => {
            this.onRedo?.();
          }
        })

        // 清空
        this.ToolButton({
          icon: '🗑️',
          label: '清空',
          isActive: false,
          onClick: () => {
            this.onClear?.();
          }
        })

        // 保存
        this.ToolButton({
          icon: '💾',
          label: '保存',
          isActive: false,
          onClick: () => {
            this.onSave?.();
          }
        })
      }
      .width('100%')
      .padding(12)
      .backgroundColor('#F5F5F5')
      .justifyContent(FlexAlign.SpaceAround)

      // 颜色选择面板
      if (this.showColorPicker) {
        this.ColorPickerPanel()
      }

      // 画笔大小选择面板
      if (this.showBrushPicker) {
        this.BrushPickerPanel()
      }
    }
  }

  /**
   * 工具按钮构建器
   */
  @Builder
  ToolButton(params: {
    icon: string,
    label: string,
    isActive: boolean,
    isDisabled?: boolean,
    onClick: () => void
  }) {
    Column() {
      Text(params.icon)
        .fontSize(20)
      Text(params.label)
        .fontSize(10)
        .fontColor(params.isDisabled ? '#CCCCCC' : (params.isActive ? '#1890FF' : '#666666'))
    }
    .padding(8)
    .borderRadius(8)
    .backgroundColor(params.isActive ? '#E6F7FF' : 'transparent')
    .opacity(params.isDisabled ? 0.5 : 1)
    .onClick(() => {
      if (!params.isDisabled) {
        params.onClick();
      }
    })
  }

  /**
   * 颜色选择面板
   */
  @Builder
  ColorPickerPanel() {
    Row() {
      ForEach(COLOR_OPTIONS, (option: ColorOption) => {
        Stack() {
          Circle()
            .width(32)
            .height(32)
            .fill(option.color)

          if (this.currentColor === option.color) {
            Circle()
              .width(16)
              .height(16)
              .fill('#FFFFFF')
          }
        }
        .margin(6)
        .onClick(() => {
          this.currentColor = option.color;
          this.showColorPicker = false;
        })
      })
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#FFFFFF')
    .justifyContent(FlexAlign.Center)
  }

  /**
   * 画笔大小选择面板
   */
  @Builder
  BrushPickerPanel() {
    Row() {
      ForEach(BRUSH_SIZES, (size: number) => {
        Stack() {
          Circle()
            .width(40)
            .height(40)
            .fill(this.currentBrushSize === size ? '#E6F7FF' : '#F5F5F5')

          Circle()
            .width(size * 2)
            .height(size * 2)
            .fill('#333333')
        }
        .margin(6)
        .onClick(() => {
          this.currentBrushSize = size;
          this.showBrushPicker = false;
        })
      })
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#FFFFFF')
    .justifyContent(FlexAlign.Center)
  }
}

关键说明:

  • 使用 @Link 双向绑定工具状态
  • @Builder 装饰器创建可复用的UI构建器
  • 颜色和画笔选择面板可展开/收起

第四步:实现绘图画布页面

4.1 创建 DrawingWorkshopPage.ets

entry/src/main/ets/pages/ 目录下创建 DrawingWorkshopPage.ets 文件:

typescript 复制代码
import { router } from '@kit.ArkUI';
import { promptAction } from '@kit.ArkUI';
import {
  DrawingTool,
  DrawingPoint,
  DrawingPath,
  COLOR_OPTIONS,
  BRUSH_SIZES
} from '../models/DrawingModels';
import { DrawingToolbar } from '../components/DrawingToolbar';
import { drawingService } from '../services/DrawingService';

/**
 * 简笔画工坊页面
 */
@Entry
@Component
struct DrawingWorkshopPage {
  // Canvas 设置
  private settings: RenderingContextSettings = new RenderingContextSettings(true);
  private canvasContext: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings);

  // 绘图状态
  @State private currentTool: DrawingTool = DrawingTool.PENCIL;
  @State private currentColor: string = COLOR_OPTIONS[0].color;
  @State private currentBrushSize: number = BRUSH_SIZES[2];
  @State private isDrawing: boolean = false;

  // 路径管理
  @State private drawingPaths: DrawingPath[] = [];
  @State private redoStack: DrawingPath[] = [];
  private currentPath: DrawingPoint[] = [];

  // 画布尺寸
  @State private canvasWidth: number = 0;
  @State private canvasHeight: number = 0;

  build() {
    Column() {
      // 顶部导航栏
      this.HeaderBar()

      // 画布区域
      Stack() {
        Canvas(this.canvasContext)
          .width('100%')
          .height('100%')
          .backgroundColor('#FFFFFF')
          .onReady(() => {
            this.initCanvas();
          })
          .onTouch((event: TouchEvent) => {
            this.handleTouch(event);
          })
          .onAreaChange((oldArea: Area, newArea: Area) => {
            this.canvasWidth = newArea.width as number;
            this.canvasHeight = newArea.height as number;
          })
      }
      .layoutWeight(1)
      .margin(12)
      .borderRadius(12)
      .clip(true)
      .shadow({
        radius: 8,
        color: 'rgba(0,0,0,0.1)',
        offsetX: 0,
        offsetY: 2
      })

      // 工具栏
      DrawingToolbar({
        currentTool: $currentTool,
        currentColor: $currentColor,
        currentBrushSize: $currentBrushSize,
        canUndo: this.drawingPaths.length > 0,
        canRedo: this.redoStack.length > 0,
        onUndo: () => this.undo(),
        onRedo: () => this.redo(),
        onClear: () => this.clearCanvas(),
        onSave: () => this.saveArtwork()
      })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F2F5')
  }

  /**
   * 顶部导航栏
   */
  @Builder
  HeaderBar() {
    Row() {
      // 返回按钮
      Text('←')
        .fontSize(24)
        .fontColor('#333333')
        .padding(12)
        .onClick(() => {
          router.back();
        })

      // 标题
      Text('简笔画工坊')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
        .layoutWeight(1)
        .textAlign(TextAlign.Center)

      // 占位
      Text('')
        .width(48)
    }
    .width('100%')
    .height(56)
    .backgroundColor('#FFFFFF')
    .shadow({
      radius: 4,
      color: 'rgba(0,0,0,0.05)',
      offsetX: 0,
      offsetY: 2
    })
  }

  /**
   * 初始化画布
   */
  private initCanvas(): void {
    this.canvasContext.fillStyle = '#FFFFFF';
    this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
  }

  /**
   * 处理触摸事件
   */
  private handleTouch(event: TouchEvent): void {
    switch (event.type) {
      case TouchType.Down:
        this.onTouchStart(event);
        break;
      case TouchType.Move:
        this.onTouchMove(event);
        break;
      case TouchType.Up:
      case TouchType.Cancel:
        this.onTouchEnd();
        break;
    }
  }

  /**
   * 触摸开始
   */
  private onTouchStart(event: TouchEvent): void {
    this.isDrawing = true;
    this.currentPath = [];

    if (event.touches && event.touches.length > 0) {
      const touch = event.touches[0];
      this.currentPath.push({
        x: touch.x,
        y: touch.y
      });
    }
  }

  /**
   * 触摸移动
   */
  private onTouchMove(event: TouchEvent): void {
    if (!this.isDrawing) return;

    if (event.touches && event.touches.length > 0) {
      const touch = event.touches[0];
      this.currentPath.push({
        x: touch.x,
        y: touch.y
      });
      this.drawCurrentPath();
    }
  }

  /**
   * 触摸结束
   */
  private onTouchEnd(): void {
    if (this.isDrawing && this.currentPath.length > 0) {
      // 保存当前路径
      const newPath: DrawingPath = {
        tool: this.currentTool,
        points: [...this.currentPath],
        color: this.currentTool === DrawingTool.PENCIL
          ? this.currentColor
          : '#FFFFFF',
        width: this.currentTool === DrawingTool.PENCIL
          ? this.currentBrushSize
          : 20
      };

      this.drawingPaths.push(newPath);
      // 清空重做栈
      this.redoStack = [];
    }

    this.isDrawing = false;
    this.currentPath = [];
  }

  /**
   * 绘制当前路径
   */
  private drawCurrentPath(): void {
    if (!this.canvasContext || this.currentPath.length < 2) return;

    // 设置绘制样式
    this.canvasContext.strokeStyle = this.currentTool === DrawingTool.PENCIL
      ? this.currentColor
      : '#FFFFFF';
    this.canvasContext.lineWidth = this.currentTool === DrawingTool.PENCIL
      ? this.currentBrushSize
      : 20;
    this.canvasContext.lineCap = 'round';
    this.canvasContext.lineJoin = 'round';

    // 绘制路径
    this.canvasContext.beginPath();
    this.canvasContext.moveTo(this.currentPath[0].x, this.currentPath[0].y);

    for (let i = 1; i < this.currentPath.length; i++) {
      this.canvasContext.lineTo(this.currentPath[i].x, this.currentPath[i].y);
    }

    this.canvasContext.stroke();
  }

  /**
   * 重绘整个画布
   */
  private redrawCanvas(): void {
    // 清空画布
    this.canvasContext.fillStyle = '#FFFFFF';
    this.canvasContext.fillRect(0, 0, this.canvasWidth, this.canvasHeight);

    // 重绘所有路径
    for (const path of this.drawingPaths) {
      this.drawPath(path);
    }
  }

  /**
   * 绘制单个路径
   */
  private drawPath(path: DrawingPath): void {
    if (path.points.length < 2) return;

    this.canvasContext.strokeStyle = path.color;
    this.canvasContext.lineWidth = path.width;
    this.canvasContext.lineCap = 'round';
    this.canvasContext.lineJoin = 'round';

    this.canvasContext.beginPath();
    this.canvasContext.moveTo(path.points[0].x, path.points[0].y);

    for (let i = 1; i < path.points.length; i++) {
      this.canvasContext.lineTo(path.points[i].x, path.points[i].y);
    }

    this.canvasContext.stroke();
  }

  /**
   * 撤销
   */
  private undo(): void {
    if (this.drawingPaths.length === 0) return;

    // 将最后一条路径移到重做栈
    const lastPath = this.drawingPaths.pop();
    if (lastPath) {
      this.redoStack.push(lastPath);
    }

    this.redrawCanvas();
  }

  /**
   * 重做
   */
  private redo(): void {
    if (this.redoStack.length === 0) return;

    // 从重做栈恢复路径
    const lastPath = this.redoStack.pop();
    if (lastPath) {
      this.drawingPaths.push(lastPath);
    }

    this.redrawCanvas();
  }

  /**
   * 清空画布
   */
  private clearCanvas(): void {
    // 显示确认对话框
    promptAction.showDialog({
      title: '清空画布',
      message: '确定要清空所有内容吗?此操作不可撤销。',
      buttons: [
        { text: '取消', color: '#666666' },
        { text: '确定', color: '#FF4D4F' }
      ]
    }).then((result) => {
      if (result.index === 1) {
        this.drawingPaths = [];
        this.redoStack = [];
        this.initCanvas();

        promptAction.showToast({
          message: '画布已清空',
          duration: 1500
        });
      }
    });
  }

  /**
   * 保存作品
   */
  private async saveArtwork(): Promise<void> {
    if (this.drawingPaths.length === 0) {
      promptAction.showToast({
        message: '请先画点什么吧',
        duration: 2000
      });
      return;
    }

    try {
      // 保存作品
      const success = await drawingService.saveArtwork(
        'default_user', // 可替换为实际用户ID
        '我的画作',
        this.drawingPaths
      );

      if (success) {
        promptAction.showToast({
          message: '作品已保存 ✨',
          duration: 2000
        });
      } else {
        promptAction.showToast({
          message: '保存失败,请重试',
          duration: 2000
        });
      }
    } catch (error) {
      console.error('保存作品失败:', error);
      promptAction.showToast({
        message: '保存失败,请重试',
        duration: 2000
      });
    }
  }
}

关键说明:

  1. Canvas 初始化

    • 使用 RenderingContextSettings 创建渲染上下文
    • onReady 回调中初始化画布
  2. 触摸事件处理

    • TouchType.Down: 开始绘制
    • TouchType.Move: 持续绘制
    • TouchType.Up: 结束绘制并保存路径
  3. 路径管理

    • drawingPaths: 已完成的路径列表
    • redoStack: 撤销的路径栈
    • currentPath: 当前正在绘制的点
  4. 绘制逻辑

    • 实时绘制当前笔画
    • 撤销/重做时重绘整个画布

第五步:配置路由

5.1 更新 main_pages.json

打开 entry/src/main/resources/base/profile/main_pages.json,添加新页面:

json 复制代码
{
  "src": [
    "pages/Index",
    "pages/DrawingWorkshopPage"
  ]
}

第六步:添加入口导航

6.1 更新 Index.ets

修改 entry/src/main/ets/pages/Index.ets,添加导航按钮:

typescript 复制代码
import { router } from '@kit.ArkUI';

@Entry
@Component
struct Index {
  @State message: string = 'MyApplication';

  build() {
    Column() {
      // 标题
      Text(this.message)
        .fontSize(32)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')
        .margin({ top: 60, bottom: 40 })

      // 功能入口按钮
      Button('进入简笔画工坊')
        .width('80%')
        .height(56)
        .fontSize(18)
        .fontColor('#FFFFFF')
        .backgroundColor('#1890FF')
        .borderRadius(28)
        .shadow({
          radius: 8,
          color: 'rgba(24, 144, 255, 0.3)',
          offsetX: 0,
          offsetY: 4
        })
        .onClick(() => {
          router.pushUrl({
            url: 'pages/DrawingWorkshopPage'
          });
        })

      // 说明文字
      Text('点击按钮开始你的创作之旅')
        .fontSize(14)
        .fontColor('#999999')
        .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Center)
  }
}

功能测试

7.1 编译运行

  1. 在 DevEco Studio 中点击 Build > Build Hap(s)/APP(s) > Build Hap(s)
  2. 连接模拟器或真机
  3. 点击运行按钮

7.2 功能测试清单

功能 测试步骤 预期结果
页面导航 点击"进入简笔画工坊"按钮 跳转到绘图页面
铅笔绘制 在画布上滑动手指 绘制黑色线条
颜色切换 点击颜色按钮,选择红色 切换后绘制红色线条
画笔大小 点击大小按钮,选择最粗 线条变粗
橡皮擦 切换橡皮,在线条上滑动 擦除线条
撤销 绘制后点击撤销 删除最后一笔
重做 撤销后点击重做 恢复最后一笔
清空 点击清空,确认 清空所有内容
保存 点击保存 显示保存成功提示
返回 点击左上角返回 返回主页

扩展功能

8.1 添加数字描红功能

创建 NumberTracingPage.ets,实现数字描红练习:

typescript 复制代码
// 简化版示例,完整代码参考 DigitalSprouting
@Entry
@Component
struct NumberTracingPage {
  @State private number: number = 0;
  @State private progress: number = 0;

  build() {
    Column() {
      // 数字显示
      Text(this.number.toString())
        .fontSize(200)
        .fontColor('#E0E0E0')
        .fontWeight(FontWeight.Bold)

      // 进度条
      Progress({ value: this.progress, total: 100 })
        .width('80%')
        .height(20)

      // 操作按钮
      Row() {
        Button('上一个')
          .onClick(() => {
            if (this.number > 0) {
              this.number--;
              this.progress = 0;
            }
          })

        Button('下一个')
          .onClick(() => {
            if (this.number < 9) {
              this.number++;
              this.progress = 0;
            }
          })
      }
      .margin({ top: 40 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

8.2 添加音效反馈

集成音效服务,在以下场景播放音效:

  • 切换工具
  • 完成绘制
  • 保存成功
  • 清空画布

8.3 添加手势识别

扩展功能以支持:

  • 双指缩放画布
  • 长按工具按钮显示更多选项
  • 摇一摇清空画布

8.4 添加作品库

创建作品管理功能:

  • 保存作品到本地数据库
  • 查看历史作品
  • 分享作品

常见问题

Q1: Canvas 不显示?

A : 确保 Canvas 组件有明确的宽高,且 onReady 回调已执行。

Q2: 触摸坐标不准确?

A: 检查 Canvas 组件的父容器是否有额外的 padding 或 margin。

Q3: 撤销/重做不工作?

A : 确保每次 onTouchEnd 时都将路径添加到 drawingPaths

Q4: 编译报错找不到模块?

A: 检查 import 路径是否正确,确保所有文件都已创建。


总结

通过本指南,您已经成功实现了:

  1. ✅ 数据模型定义(DrawingModels)
  2. ✅ 绘图服务(DrawingService)
  3. ✅ 工具栏组件(DrawingToolbar)
  4. ✅ 绘图画布页面(DrawingWorkshopPage)
  5. ✅ 路由配置
  6. ✅ 入口导航

这个简笔画工坊具备了完整的绘图功能,您可以在此基础上继续扩展更多特性。


参考资源


效果

源代码

https://gitcode.com/daleishen/jianbihua/tree/main

班级链接

https://developer.huawei.com/consumer/cn/training/classDetail/fd34ff9286174e848d34cde7f512ce22?type=1%3Fha_source%3Dhmosclass\&ha_sourceId=89000248

相关推荐
IT考试认证4 小时前
华为AI认证 H13-323 HCIP-AI Solution Architect 题库
人工智能·华为·题库·hcip-ai·h13-323
爱笑的眼睛114 小时前
ArkTS接口与泛型在HarmonyOS应用开发中的深度应用
华为·harmonyos
大雷神6 小时前
【鸿蒙星光分享】HarmonyOS 语音朗读功能同步教程
华为·harmonyos
不凡的凡6 小时前
flutter 管理工具fvm
flutter·harmonyos
柒儿吖6 小时前
Electron for HarmonyOS_PC Swifty 密码管理器适配开源鸿蒙PC开发实践
javascript·electron·harmonyos
一只栖枝7 小时前
HarmonyOS 开发高级认证是什么?含金量高吗?
华为·华为认证·harmonyos·鸿蒙·考证
柒儿吖8 小时前
Electron for 鸿蒙PC - 菜单栏完整开发指南:从原生菜单到自定义菜单的实现
javascript·electron·harmonyos
A懿轩A8 小时前
【2025最新】最新HarmonyOS 6 DevEco Studio下载安装 详细步骤(带图展示)
华为·harmonyos
进击的阿三姐8 小时前
鸿蒙个人开发者账号如何真机调试
华为·harmonyos