HarmonyOS NEXT 实战:开发一个实用的文本分析器应用

HarmonyOS NEXT 实战:开发一个实用的文本分析器应用

本文详细记录了使用 HarmonyOS NEXT 开发文本分析器的完整过程,涵盖实时文本统计、正则表达式应用、自定义组件封装、剪贴板操作等核心技术点,适合初学者入门实战。

一、项目背景

在日常工作中,我们经常需要统计文本的字数、词数、句子数等信息,比如:

  • 写论文时检查字数是否达标
  • 翻译时统计原文词数
  • 编辑文案时了解文本结构

虽然网上有很多在线工具,但每次都要打开浏览器、复制粘贴,不够方便。如果能有一个手机上的原生应用,随时粘贴文本就能看到统计结果,那该多好!

本文将带领大家使用 HarmonyOS NEXT 开发一个功能完善的文本分析器应用,最终实现:

  • ✅ 实时统计总字符数
  • ✅ 统计无空格字符数
  • ✅ 智能统计中英文单词数
  • ✅ 统计句子数量
  • ✅ 统计行数
  • ✅ 一键清空、复制结果、加载示例

二、开发环境

项目 版本
DevEco Studio 5.0.3.403
HarmonyOS SDK API 23(6.1.0)
设备类型 Phone
项目模型 Stage 模型

三、项目结构

复制代码
MyApplication/
├── AppScope/
│   └── app.json5                    # 应用全局配置
├── entry/
│   ├── src/main/
│   │   ├── ets/
│   │   │   ├── entryability/
│   │   │   │   └── EntryAbility.ets # 应用入口
│   │   │   └── pages/
│   │   │       └── Index.ets        # 主页面(核心代码)
│   │   ├── resources/
│   │   │   └── base/
│   │   │       ├── element/
│   │   │       │   └── string.json  # 字符串资源
│   │   │       └── media/           # 图片资源
│   │   └── module.json5             # 模块配置
│   ├── build-profile.json5          # 构建配置
│   └── oh-package.json5             # 依赖配置
├── build-profile.json5              # 项目构建配置
└── hvigorfile.ts                    # 构建脚本

四、核心功能设计

4.1 需求分析

文本分析器需要统计以下指标:

统计项 说明 计算方式
总字符 包含空格、换行等所有字符 text.length
无空格字符 排除空格、换行后的字符数 正则替换后统计
单词数 英文单词 + 中文字符 正则匹配计数
句子数 按句末标点分割 正则分割后计数
行数 按换行符分割 split('\n').length

4.2 状态变量设计

typescript 复制代码
@Entry
@Component
struct Index {
  @State textInput: string = '';          // 输入的文本
  @State charTotal: number = 0;           // 总字符数
  @State charNoSpace: number = 0;         // 无空格字符数
  @State wordCount: number = 0;           // 单词数
  @State sentenceCount: number = 0;       // 句子数
  @State lineCount: number = 0;           // 行数
  @State copyFeedback: string = '';       // 复制反馈提示

  private inputController: TextAreaController = new TextAreaController();
}

五、核心算法实现

5.1 统计函数设计

typescript 复制代码
updateStats(text: string): void {
  // 基础统计
  this.charTotal = text.length;
  this.charNoSpace = text.replace(/[\s\r\n]/g, '').length;
  this.lineCount = text ? text.split('\n').length : 0;

  // 空文本特殊处理
  if (!text.trim()) {
    this.wordCount = 0;
    this.sentenceCount = 0;
    return;
  }

  let cleaned = text.trim();

  // 单词数统计(中英文混合)
  let enWords = cleaned.match(/[a-zA-Z0-9]+(?:['-][a-zA-Z0-9]+)*/g) || [];
  let cnChars = cleaned.match(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/g) || [];
  this.wordCount = enWords.length + cnChars.length;

  // 句子数统计
  let sentenceSplits = cleaned.split(/[。!?.!?\n]+/).filter(s => s.trim().length > 0);
  this.sentenceCount = Math.max(sentenceSplits.length, cleaned.length > 0 ? 1 : 0);
}

5.2 正则表达式详解

5.2.1 无空格字符统计
typescript 复制代码
text.replace(/[\s\r\n]/g, '').length
  • [\s\r\n]:匹配所有空白字符(空格、制表符、换行等)
  • g:全局匹配
  • 替换为空字符串后统计长度
5.2.2 英文单词匹配
typescript 复制代码
/[a-zA-Z0-9]+(?:['-][a-zA-Z0-9]+)*/g

这个正则表达式能处理:

  • 普通单词:helloworld
  • 带连字符:state-of-the-art
  • 带撇号:don'tit's

解析

  • [a-zA-Z0-9]+:匹配字母数字组合
  • (?:['-][a-zA-Z0-9]+)*:非捕获组,匹配可选的连字符/撇号后跟字母数字
5.2.3 中文字符匹配
typescript 复制代码
/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/g
  • \u4e00-\u9fff:CJK 统一汉字(常用字)
  • \u3400-\u4dbf:CJK 扩展 A 区
  • \uf900-\ufaff:CJK 兼容汉字
5.2.4 句子分割
typescript 复制代码
/[。!?.!?\n]+/g

支持的句末标点:

  • 中文:
  • 英文:. ! ?
  • 换行符:\n

5.3 中英文混合统计策略

对于中英文混合文本,采用"分治"策略:

typescript 复制代码
// 英文单词数
let enWords = cleaned.match(/[a-zA-Z0-9]+(?:['-][a-zA-Z0-9]+)*/g) || [];

// 中文字符数(每个汉字算一个"词")
let cnChars = cleaned.match(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/g) || [];

// 总词数
this.wordCount = enWords.length + cnChars.length;

示例

  • 输入:"Hello 世界!这是 HarmonyOS 应用。"
  • 英文单词:HelloHarmonyOS → 2 个
  • 中文字符: → 6 个
  • 总词数:2 + 6 = 8

六、UI 界面设计

6.1 整体布局结构

typescript 复制代码
build() {
  Column() {
    // 1. 标题区域
    Column() { ... }
    
    // 2. 统计卡片区域 --- 第一行
    Row() { ... }
    
    // 3. 统计卡片区域 --- 第二行
    Row() { ... }
    
    // 4. 输入区域标签
    Row() { ... }
    
    // 5. 文本输入区
    TextArea({ ... })
    
    // 6. 底部操作栏
    Row() { ... }
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#FFF2F2F7')
}

6.2 标题区域

typescript 复制代码
Column() {
  Text('文本分析器')
    .fontSize(26)
    .fontWeight(FontWeight.Bold)
    .fontColor('#FF6200EE')

  Text('实时统计你的文本信息')
    .fontSize(14)
    .fontColor('#99000000')
    .margin({ top: 4 })
}
.width('100%')
.padding({ top: 20, bottom: 12 })
.alignItems(HorizontalAlign.Center)

6.3 统计卡片组件封装

使用 @Builder 装饰器封装可复用的统计卡片:

typescript 复制代码
@Builder
statCard(label: string, value: number, color: string) {
  Column() {
    Text(`${value}`)
      .fontSize(32)
      .fontWeight(FontWeight.Bold)
      .fontColor(color)

    Text(label)
      .fontSize(13)
      .fontColor('#99000000')
      .margin({ top: 2 })
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
  .backgroundColor(Color.White)
  .borderRadius(12)
  .margin(4)
  .shadow({ radius: 3, color: '#0A000000', offsetY: 2 })
}

使用示例

typescript 复制代码
// 第一行:2 个卡片
Row() {
  this.statCard('总字符', this.charTotal, '#FF007AFF')
  this.statCard('无空格', this.charNoSpace, '#FF34C759')
}
.width('100%')
.padding({ left: 12, right: 12 })
.height(90)

// 第二行:3 个卡片
Row() {
  this.statCard('单词数', this.wordCount, '#FFFF9500')
  this.statCard('句子数', this.sentenceCount, '#FFFF2D55')
  this.statCard('行数', this.lineCount, '#FF5856D6')
}
.width('100%')
.padding({ left: 12, right: 12 })
.height(90)

颜色设计

  • 总字符:蓝色 #007AFF
  • 无空格:绿色 #34C759
  • 单词数:橙色 #FF9500
  • 句子数:粉色 #FF2D55
  • 行数:紫色 #5856D6

6.4 输入区域标签

typescript 复制代码
Row() {
  Text('输入文本')
    .fontSize(16)
    .fontWeight(FontWeight.Medium)

  Text(`${this.textInput.length}`)
    .fontSize(14)
    .fontColor('#99000000')
    .margin({ left: 8 })

  Text('/ 10000')
    .fontSize(12)
    .fontColor('#66000000')

  Blank()

  // 清空按钮(有内容时显示)
  if (this.textInput.length > 0) {
    Text('×')
      .fontSize(18)
      .fontColor('#66000000')
      .width(28)
      .height(28)
      .borderRadius(14)
      .backgroundColor('#15000000')
      .textAlign(TextAlign.Center)
      .onClick(() => {
        this.textInput = '';
        this.updateStats('');
      })
  }
}
.width('100%')
.padding({ left: 16, right: 16, top: 8 })
.alignItems(VerticalAlign.Center)

设计亮点

  • 显示字符计数:23 / 10000
  • 右侧有清空按钮(×),点击即可清空
  • 按钮仅在输入框有内容时显示

6.5 文本输入区

typescript 复制代码
TextArea({ text: this.textInput, placeholder: '在此粘贴或输入文本...' })
  .width('100%')
  .layoutWeight(1)  // 自动填充剩余空间
  .padding(12)
  .backgroundColor(Color.White)
  .borderRadius(12)
  .margin({ left: 12, right: 12, bottom: 4 })
  .placeholderFont({ size: 14 })
  .placeholderColor('#66000000')
  .fontSize(16)
  .maxLength(10000)  // 最大输入 10000 字符
  .onChange((value: string) => {
    this.textInput = value;
    this.updateStats(value);  // 实时更新统计
  })

实时更新 :通过 onChange 回调,每次输入变化都触发统计更新。

6.6 底部操作栏

typescript 复制代码
Row() {
  // 清空按钮
  Button('清空')
    .fontSize(14)
    .fontColor('#FF007AFF')
    .backgroundColor('#FFE5F0FF')
    .borderRadius(20)
    .height(40)
    .layoutWeight(1)
    .margin({ right: 6 })
    .onClick(() => {
      this.textInput = '';
      this.inputController.caretPosition(0);
      this.updateStats('');
    })

  // 复制结果按钮
  Button('复制结果')
    .fontSize(14)
    .fontColor(Color.White)
    .backgroundColor('#FF007AFF')
    .borderRadius(20)
    .height(40)
    .layoutWeight(1)
    .margin({ left: 6, right: 6 })
    .onClick(() => {
      let statsText = `📊 文本统计\n总字符: ${this.charTotal}\n无空格: ${this.charNoSpace}\n单词数: ${this.wordCount}\n句子数: ${this.sentenceCount}\n行数: ${this.lineCount}`;
      this.copyToClipboard(statsText);
    })

  // 示例文本按钮
  Button('示例')
    .fontSize(14)
    .fontColor('#FFFF9500')
    .backgroundColor('#FFFFF5E0')
    .borderRadius(20)
    .height(40)
    .layoutWeight(1)
    .margin({ left: 6 })
    .onClick(() => {
      this.textInput = '你好,欢迎使用文本分析器!\n这是一个简单的鸿蒙原生应用。\n它可以统计:总字符数、无空格字符数、单词数、句子数和行数。\n快去试试吧!';
      this.inputController.caretPosition(this.textInput.length);
      this.updateStats(this.textInput);
    })
}
.width('100%')
.padding({ left: 12, right: 12, bottom: 12 })
.height(60)

三个按钮功能

按钮 颜色 功能
清空 浅蓝色背景 清空输入内容
复制结果 蓝色背景 生成统计报告并提示
示例 浅橙色背景 加载示例文本

6.7 复制反馈提示

typescript 复制代码
if (this.copyFeedback) {
  Text(this.copyFeedback)
    .fontSize(13)
    .fontColor(Color.White)
    .backgroundColor('#CC000000')
    .borderRadius(16)
    .padding({ left: 16, right: 16, top: 6, bottom: 6 })
    .transition({ type: TransitionType.Insert, opacity: 0 })
}

反馈函数

typescript 复制代码
copyToClipboard(text: string): void {
  this.showFeedback('📋 统计结果已生成,可直接长按选中复制');
}

showFeedback(msg: string): void {
  this.copyFeedback = msg;
  setTimeout(() => {
    this.copyFeedback = '';
  }, 2000);  // 2秒后自动消失
}

七、完整代码

typescript 复制代码
@Entry
@Component
struct Index {
  @State textInput: string = '';
  @State charTotal: number = 0;
  @State charNoSpace: number = 0;
  @State wordCount: number = 0;
  @State sentenceCount: number = 0;
  @State lineCount: number = 0;
  @State copyFeedback: string = '';

  private inputController: TextAreaController = new TextAreaController();

  aboutToAppear(): void {
    this.updateStats('');
  }

  build() {
    Column() {
      // 标题区域
      Column() {
        Text('文本分析器')
          .fontSize(26)
          .fontWeight(FontWeight.Bold)
          .fontColor('#FF6200EE')

        Text('实时统计你的文本信息')
          .fontSize(14)
          .fontColor('#99000000')
          .margin({ top: 4 })
      }
      .width('100%')
      .padding({ top: 20, bottom: 12 })
      .alignItems(HorizontalAlign.Center)

      // 统计卡片区域 --- 第一行
      Row() {
        this.statCard('总字符', this.charTotal, '#FF007AFF')
        this.statCard('无空格', this.charNoSpace, '#FF34C759')
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .height(90)

      // 统计卡片区域 --- 第二行
      Row() {
        this.statCard('单词数', this.wordCount, '#FFFF9500')
        this.statCard('句子数', this.sentenceCount, '#FFFF2D55')
        this.statCard('行数', this.lineCount, '#FF5856D6')
      }
      .width('100%')
      .padding({ left: 12, right: 12 })
      .height(90)

      // 输入区域标签
      Row() {
        Text('输入文本')
          .fontSize(16)
          .fontWeight(FontWeight.Medium)

        Text(`${this.textInput.length}`)
          .fontSize(14)
          .fontColor('#99000000')
          .margin({ left: 8 })

        Text('/ 10000')
          .fontSize(12)
          .fontColor('#66000000')

        Blank()

        if (this.textInput.length > 0) {
          Text('×')
            .fontSize(18)
            .fontColor('#66000000')
            .width(28)
            .height(28)
            .borderRadius(14)
            .backgroundColor('#15000000')
            .textAlign(TextAlign.Center)
            .onClick(() => {
              this.textInput = '';
              this.updateStats('');
            })
        }
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 8 })
      .alignItems(VerticalAlign.Center)

      // 文本输入区
      TextArea({ text: this.textInput, placeholder: '在此粘贴或输入文本...' })
        .width('100%')
        .layoutWeight(1)
        .padding(12)
        .backgroundColor(Color.White)
        .borderRadius(12)
        .margin({ left: 12, right: 12, bottom: 4 })
        .placeholderFont({ size: 14 })
        .placeholderColor('#66000000')
        .fontSize(16)
        .maxLength(10000)
        .onChange((value: string) => {
          this.textInput = value;
          this.updateStats(value);
        })

      // 底部操作栏
      Row() {
        Button('清空')
          .fontSize(14)
          .fontColor('#FF007AFF')
          .backgroundColor('#FFE5F0FF')
          .borderRadius(20)
          .height(40)
          .layoutWeight(1)
          .margin({ right: 6 })
          .onClick(() => {
            this.textInput = '';
            this.inputController.caretPosition(0);
            this.updateStats('');
          })

        Button('复制结果')
          .fontSize(14)
          .fontColor(Color.White)
          .backgroundColor('#FF007AFF')
          .borderRadius(20)
          .height(40)
          .layoutWeight(1)
          .margin({ left: 6, right: 6 })
          .onClick(() => {
            let statsText = `📊 文本统计\n总字符: ${this.charTotal}\n无空格: ${this.charNoSpace}\n单词数: ${this.wordCount}\n句子数: ${this.sentenceCount}\n行数: ${this.lineCount}`;
            this.copyToClipboard(statsText);
          })

        Button('示例')
          .fontSize(14)
          .fontColor('#FFFF9500')
          .backgroundColor('#FFFFF5E0')
          .borderRadius(20)
          .height(40)
          .layoutWeight(1)
          .margin({ left: 6 })
          .onClick(() => {
            this.textInput = '你好,欢迎使用文本分析器!\n这是一个简单的鸿蒙原生应用。\n它可以统计:总字符数、无空格字符数、单词数、句子数和行数。\n快去试试吧!';
            this.inputController.caretPosition(this.textInput.length);
            this.updateStats(this.textInput);
          })
      }
      .width('100%')
      .padding({ left: 12, right: 12, bottom: 12 })
      .height(60)

      // 复制反馈提示
      if (this.copyFeedback) {
        Text(this.copyFeedback)
          .fontSize(13)
          .fontColor(Color.White)
          .backgroundColor('#CC000000')
          .borderRadius(16)
          .padding({ left: 16, right: 16, top: 6, bottom: 6 })
          .transition({ type: TransitionType.Insert, opacity: 0 })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFF2F2F7')
  }

  @Builder
  statCard(label: string, value: number, color: string) {
    Column() {
      Text(`${value}`)
        .fontSize(32)
        .fontWeight(FontWeight.Bold)
        .fontColor(color)

      Text(label)
        .fontSize(13)
        .fontColor('#99000000')
        .margin({ top: 2 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .margin(4)
    .shadow({ radius: 3, color: '#0A000000', offsetY: 2 })
  }

  updateStats(text: string): void {
    this.charTotal = text.length;
    this.charNoSpace = text.replace(/[\s\r\n]/g, '').length;
    this.lineCount = text ? text.split('\n').length : 0;

    if (!text.trim()) {
      this.wordCount = 0;
      this.sentenceCount = 0;
      return;
    }

    let cleaned = text.trim();

    // 英文单词(含连字符和缩写)
    let enWords = cleaned.match(/[a-zA-Z0-9]+(?:['-][a-zA-Z0-9]+)*/g) || [];
    // 中文字符(每个字独立成"词")
    let cnChars = cleaned.match(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/g) || [];
    this.wordCount = enWords.length + cnChars.length;

    // 句子数
    let sentenceSplits = cleaned.split(/[。!?.!?\n]+/).filter(s => s.trim().length > 0);
    this.sentenceCount = Math.max(sentenceSplits.length, cleaned.length > 0 ? 1 : 0);
  }

  copyToClipboard(text: string): void {
    this.showFeedback('📋 统计结果已生成,可直接长按选中复制');
  }

  showFeedback(msg: string): void {
    this.copyFeedback = msg;
    setTimeout(() => {
      this.copyFeedback = '';
    }, 2000);
  }
}

八、运行效果

九、踩坑记录

9.1 中文字符统计问题

问题 :使用 \w 正则匹配单词时,中文字符被忽略。

原因\w 只匹配 [a-zA-Z0-9_],不包含中文。

解决:单独匹配中文字符,使用 Unicode 范围:

typescript 复制代码
let cnChars = cleaned.match(/[\u4e00-\u9fff\u3400-\u4dbf\uf900-\ufaff]/g) || [];

9.2 英文缩写和连字符处理

问题don'tstate-of-the-art 被错误地拆分为多个词。

原因 :简单的单词正则 [a-zA-Z]+ 无法处理撇号和连字符。

解决:使用更完善的正则:

typescript 复制代码
/[a-zA-Z0-9]+(?:['-][a-zA-Z0-9]+)*/g

9.3 句子数为 0 的边界情况

问题:输入一段没有标点的文字时,句子数显示为 0。

原因:分割后的空数组长度为 0。

解决 :使用 Math.max 确保至少为 1:

typescript 复制代码
this.sentenceCount = Math.max(sentenceSplits.length, cleaned.length > 0 ? 1 : 0);

9.4 TextArea 的 text 属性绑定

问题TextArea({ text: this.textInput }) 不能直接修改。

原因 :ArkTS 的单向数据流,需要通过 onChange 更新状态。

解决 :在 onChange 中同步更新状态变量:

typescript 复制代码
.onChange((value: string) => {
  this.textInput = value;
  this.updateStats(value);
})

十、总结与扩展

10.1 项目亮点

  1. 实时统计:输入即统计,无需点击按钮
  2. 中英文支持:智能识别中英文混合文本
  3. 组件复用@Builder 封装统计卡片
  4. 用户体验:字符计数、清空按钮、示例文本
  5. 视觉设计:卡片式布局、颜色区分、阴影效果

10.2 可扩展功能

  • 支持更多统计维度(段落数、平均词长、阅读时间)
  • 历史记录保存
  • 导出统计报告(文本/图片)
  • 深色模式适配
  • 多语言支持

10.3 学习收获

知识点 在项目中的应用
@State 装饰器 响应式状态管理
@Builder 装饰器 组件封装复用
正则表达式 文本分析统计
TextArea 组件 多行文本输入
条件渲染 动态显示/隐藏元素
setTimeout 延时隐藏提示

十一、参考资料


如果这篇文章对你有帮助,欢迎点赞、收藏、评论!有任何问题也可以留言讨论~ 🚀

相关推荐
木咺吟23 分钟前
鸿蒙原生应用实战(二):游戏库列表与筛选排序 — 卡片式UI设计
harmonyos
互联网散修2 小时前
鸿蒙实战:从零实现自定义相机(下)——填平预览拉伸、比例错乱、缩略图消失的六大坑
数码相机·华为·harmonyos
风华圆舞2 小时前
鸿蒙 + Flutter 下 AI 助手为什么要支持流式输出
人工智能·flutter·harmonyos
金启攻3 小时前
【鸿蒙原生应用实战】第四篇:打包清单——勾选交互、进度计算与实用工具
harmonyos
Swift社区3 小时前
鸿蒙 App 卡顿分析:定位方法 + 优化代码实战
华为·harmonyos
坚果派·白晓明3 小时前
鸿蒙 PC 应用集成 libhv 鸿蒙化三方库 —— AtomCode + Skills 驱动的高效集成实践
c语言·c++·ai编程·harmonyos·atomcode
祭曦念4 小时前
【共创季稿事节】HarmonyOS动态任务列表开发实战
华为·harmonyos
祭曦念5 小时前
【共创季稿事节】鸿蒙原生ArkTS动态列表布局实战_State_ForEach完整指南
华为·harmonyos
不羁的木木5 小时前
《HarmonyOS 6.1 新能力实战之智感握姿》第二篇:核心功能——查询与监听握持手状态
华为·harmonyos
风华圆舞6 小时前
鸿蒙 + Flutter 下 AI 页面的状态协同设计
人工智能·flutter·harmonyos