KMP 实现鸿蒙跨端:Kotlin 学习打卡统计与习惯分析工具

目录

  1. 概述
  2. 功能设计
  3. Kotlin 实现代码(KMP)
  4. JavaScript 调用示例
  5. ArkTS 页面集成与调用
  6. 数据输入与交互体验
  7. 编译与自动复制流程
  8. 总结

概述

本案例在 Kotlin Multiplatform (KMP) 工程中实现了一个 学习打卡统计与习惯分析工具

  • 输入:一串只包含 0/1 的记录,使用空格分隔,例如:1 1 0 1 1 1 0
  • 输出:
    • 基础统计:记录天数、打卡天数、缺卡天数、打卡成功率
    • 深度分析:最长连续打卡天数
    • 习惯评语:根据数据给出“习惯是否养成”的文字建议
  • 技术路径:Kotlin → Kotlin/JS → JavaScript 模块 → ArkTS 页面调用。

这个案例和“个人收支预算分析工具”“成绩排行榜生成与分析工具”等前面的案例一样,继续强调一个思路:

把算法逻辑写在 Kotlin 里,一次实现,多端复用;把交互界面写在 ArkTS 里,专注 UI 和体验。

Kotlin 侧负责解析打卡数据、计算统计指标和生成解释性文本;ArkTS 侧只需要把输入字符串传给 Kotlin 函数,并把返回结果原样展示出来即可。借助 KMP 的 Kotlin/JS 能力,这个分析工具可以在 Node.js、Web 前端以及 OpenHarmony 中复用相同的代码逻辑。


功能设计

输入数据格式

学习打卡工具采用非常简单的输入格式:

  • 使用 空格分隔 每一天的打卡记录。
  • 1 表示当天打卡成功0 表示当天未打卡
  • 输入示例:
1 1 0 1 1 1 0

这可以理解为最近 7 天的学习情况:

  • 第 1 天:打卡
  • 第 2 天:打卡
  • 第 3 天:缺卡
  • 第 4 天:打卡
  • 第 5 天:打卡
  • 第 6 天:打卡
  • 第 7 天:缺卡

工具会基于这串数据计算出:

  • 一共记录了几天
  • 其中有几天打卡成功
  • 有几天缺卡
  • 打卡成功率是多少
  • 连续打卡最长是多少天

输出信息结构

为了便于在 ArkTS 页面以及终端中直接展示,Kotlin 函数返回的是一段结构化的多行文本,划分为几个分区:

  1. 标题区:例如“📚 学习打卡统计与习惯分析”,一眼看出工具用途。
  2. 基础统计:记录总天数、打卡天数、缺卡天数、成功率等指标。
  3. 最长连续打卡:帮助用户了解自己最长坚持了多久。
  4. 习惯养成建议:根据数据给出一句话建议,例如“习惯已经基本养成”或“建议从每天 10 分钟开始”。

这样的输出结构使得:

  • 在 ArkTS 中可以直接把整段文本绑定到 Text 组件,配合 monospace 字体,阅读体验类似终端报告。
  • 如果将来想把结果保存到日志或者后端,直接保存字符串即可。
  • 需要更精细的 UI 时,也可以在前端根据分隔符进行拆分,再按块展示。

Kotlin 实现代码(KMP)

核心代码在 src/jsMain/kotlin/App.kt 中,通过 @JsExport 导出:

@OptIn(ExperimentalJsExport::class)
@JsExport
fun studyHabitAnalyzer(inputText: String = "1 1 0 1 1 1 0"): String {
    // 输入格式: 一串用空格分隔的 0/1,1 表示当天有打卡,0 表示未打卡
    val parts = inputText.trim().split(" ").filter { it.isNotEmpty() }

    if (parts.isEmpty()) {
        return "❌ 错误: 请输入至少一个 0 或 1,例如: 1 1 0 1 1 1 0"
    }

    val records = parts.mapNotNull { it.toIntOrNull() }.filter { it == 0 || it == 1 }

    if (records.isEmpty()) {
        return "❌ 错误: 输入中没有有效的 0 或 1\n示例: 1 1 0 1 1 1 0"
    }

    val totalDays = records.size
    val punchDays = records.count { it == 1 }
    val missDays = totalDays - punchDays
    val successRate = (punchDays * 1000 / totalDays) / 10.0

    // 计算最长连续打卡天数
    var currentStreak = 0
    var maxStreak = 0
    for (v in records) {
        if (v == 1) {
            currentStreak++
            if (currentStreak > maxStreak) maxStreak = currentStreak
        } else {
            currentStreak = 0
        }
    }

    val comment = when {
        successRate >= 90.0 && maxStreak >= 7 -> "习惯已经基本养成,保持当前节奏即可。"
        successRate >= 70.0 && maxStreak >= 3 -> "已经有不错的坚持记录,可以尝试设定更长的连续打卡目标。"
        successRate >= 50.0 -> "偶尔打卡,建议固定时间段进行学习打卡,提升稳定性。"
        else -> "打卡较少,建议先从每天 10 分钟的小目标开始,逐步建立习惯。"
    }

    return "📚 学习打卡统计与习惯分析\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "原始记录: $inputText\n\n" +
           "1️⃣ 基础统计:\n" +
           "  记录天数: ${totalDays} 天\n" +
           "  打卡天数: ${punchDays} 天\n" +
           "  缺卡天数: ${missDays} 天\n" +
           "  打卡成功率: ${successRate}%\n\n" +
           "2️⃣ 最长连续打卡:\n" +
           "  最长连续打卡: ${maxStreak} 天\n\n" +
           "3️⃣ 习惯养成建议:\n" +
           "  $comment\n\n" +
           "━━━━━━━━━━━━━━━━━━━━━\n" +
           "✅ 分析完成!"
}

代码说明

  • 数据解析
    • 使用 split(" ") 将输入拆分为字符串列表,通过 filter { it.isNotEmpty() } 自动忽略多余空格。
    • 再使用 toIntOrNull() 将其转为整数,仅保留等于 01 的项,排除非法输入。
  • 容错处理
    • 如果一开始就没有任何有效的片段,或者所有片段都不是 0/1,则直接返回带有示例的错误提示文本。
  • 统计指标
    • totalDays 是记录的总长度。
    • punchDays 是所有为 1 的个数,missDays 是差值。
    • successRate 为打卡成功率,这里使用 (punchDays * 1000 / totalDays) / 10.0 保留一位小数。
  • 最长连续打卡
    • 用一个简单的线性扫描,currentStreak 记录当前连续 1 的长度,maxStreak 记录历史最大值。
  • 习惯评语
    • 使用 when 表达式,根据成功率和最长连续天数分档:
      • 成功率 >= 90% 且最长连续 >= 7 天:认为习惯基本养成。
      • 成功率 >= 70% 且最长连续 >= 3 天:状态不错,鼓励更进一步。
      • 成功率 >= 50%:偶尔打卡,建议固定时间段。
      • 否则:建议先从更小目标开始,降低门槛。

这段 Kotlin 代码全部运行在 jsMain 源集中,通过 @JsExport 导出为 JS,可以被 openHarmony ArkTS 代码直接使用。


JavaScript 调用示例

Kotlin 编译后会生成 hellokjs.mjs 和对应的类型声明 hellokjs.d.ts。在支持 ES Module 的 JavaScript 环境中,可以这样调用:

// 导入 Kotlin/JS 编译生成的模块
import { studyHabitAnalyzer } from './hellokjs.mjs';

// 示例 1:使用默认参数
const result1 = studyHabitAnalyzer();
console.log(result1);

// 示例 2:自定义一段打卡记录
const result2 = studyHabitAnalyzer('1 0 1 1 0 1 0 1 1 1');
console.log(result2);

在浏览器或 Node.js 中运行时,你会在控制台看到类似这样的文本报告:

📚 学习打卡统计与习惯分析
━━━━━━━━━━━━━━━━━━━━━
原始记录: 1 0 1 1 0 1 0 1 1 1

1️⃣ 基础统计:
  记录天数: 10 天
  打卡天数: 7 天
  缺卡天数: 3 天
  打卡成功率: 70.0%

2️⃣ 最长连续打卡:
  最长连续打卡: 3 天

3️⃣ 习惯养成建议:
  已经有不错的坚持记录,可以尝试设定更长的连续打卡目标。

━━━━━━━━━━━━━━━━━━━━━
✅ 分析完成!

通过这种方式,你可以先在纯 Kotlin/JS 或 Node 环境中调试算法逻辑,确认输出符合预期之后,再集成到 ArkTS 页面中使用,避免来回改动 UI 层代码。


ArkTS 页面集成与调用

在 OpenHarmony 应用 kmp_ceshiapp 中,我们已经将首页 Index.ets 改造成“学习打卡统计与习惯分析”页面,其核心调用关系如下:

import { studyHabitAnalyzer } from './hellokjs';

@Entry
@Component
struct Index {
  @State message: string = '请输入一串 0/1 表示每天是否打卡';
  @State inputText: string = '1 1 0 1 1 1 0';
  @State resultText: string = '';

  aboutToAppear(): void {
    this.analyzeStudyHabit();
  }

  analyzeStudyHabit(): void {
    try {
      const input: string = this.inputText;
      const result: string = studyHabitAnalyzer(input);
      this.resultText = result;
      this.message = '✓ 计算完成';
    } catch (error) {
      const errorMessage = error instanceof Error ? error.message : String(error);
      this.message = `✗ 错误: ${errorMessage}`;
    }
  }

  build() {
    // 省略 UI 细节,这里重点展示状态和调用关系
  }
}

集成要点

  • ArkTS 通过 import { studyHabitAnalyzer } from './hellokjs'; 直接导入 Kotlin/JS 导出的函数。
  • 所有状态都用 @State 修饰,保证 UI 自动随数据变化而更新:
    • inputText 用于收集用户的 0/1 输入。
    • resultText 存放 Kotlin 返回的多行文本结果。
    • message 显示当前状态,如“✓ 计算完成”或错误提示。
  • aboutToAppear() 生命周期钩子中直接调用 analyzeStudyHabit(),让用户打开页面就能看到一个默认示例分析结果。

这使得 ArkTS 端只负责输入、显示和简单的错误消息处理,计算逻辑完全由 Kotlin 提供。


数据输入与交互体验

在 ArkTS 页面中,输入和交互部分大致结构如下:

// 输入区域
Column() {
  Text('输入一串 0/1 记录打卡 (示例: 1 1 0 1 1 1 0):')
    .fontSize(14)
    .fontWeight(FontWeight.Bold)
    .fontColor('#1f2937')
    .margin({ bottom: 8 })

  TextInput({ placeholder: '例如: 1 1 0 1 1 1 0', text: this.inputText })
    .width('100%')
    .height(80)
    .padding(8)
    .border({ width: 1, color: '#d1d5db' })
    .borderRadius(6)
    .onChange((value: string) => {
      this.inputText = value;
    })
    .margin({ bottom: 12 })

  Button('开始分析')
    .width('100%')
    .height(40)
    .margin({ top: 4 })
    .backgroundColor('#f59e0b')
    .fontColor(Color.White)
    .onClick(() => {
      this.analyzeStudyHabit();
    })
}

在结果显示区域,通过一个可滚动的 Scroll + Text 组合来展示多行分析文本,并使用等宽字体保持布局整洁。底部还提供了“默认示例”和“清空”两个按钮,方便用户快速体验和重新输入:

  • 默认示例
    • inputText 重置为 1 1 0 1 1 1 0,并立即调用 analyzeStudyHabit()
  • 清空
    • 清空 inputTextresultText,并把 message 恢复为“请输入一串 0/1 表示每天是否打卡”。

整体交互流程非常轻量,重点在于让用户迅速看到自己最近一段时间的学习打卡表现和建议。


编译与自动复制流程

和其它案例一样,学习打卡工具的 Kotlin 代码编译和 JS 文件同步由项目根目录下的脚本自动完成。你只需要在 Windows 下执行:

cd d:\flutter_Obj\kmp_openharmony
.\build-and-copy.bat

脚本会自动:

  1. 执行 gradlew build,编译 Kotlin/JS 工程。
  2. 检查输出目录 build/js/packages/hellokjs/kotlin/ 中是否存在:
    • hellokjs.mjs
    • hellokjs.d.ts
  3. 将上述文件复制到 ArkTS pages 目录:
    • kmp_ceshiapp/entry/src/main/ets/pages/hellokjs.d.ts
    • kmp_ceshiapp/entry/src/main/ets/pages/hellokjs.js(由 hellokjs.mjs 复制并重命名)。

这样,当你在 App.kt 中新增或修改 studyHabitAnalyzer 时,只要重新执行一次脚本,就能让 ArkTS 端立刻使用到最新的逻辑,无需手动拷贝或修改路径。


总结

通过“学习打卡统计与习惯分析工具”这个案例,我们再一次完整走通了从 Kotlin 到 JS 再到 ArkTS 的跨端链路:

  1. 使用 Kotlin 实现一个围绕“学习习惯”的小算法工具,专注数据解析和统计计算。
  2. 借助 Kotlin/JS 和 @JsExport,生成可在 JavaScript / ArkTS 环境中直接调用的 ES Module 与类型声明文件。
  3. 在 OpenHarmony 的 ArkTS 页面中,通过简单的 import 和状态绑定,实现交互式的打卡分析界面。

这个工具与之前的 BMI 计算、贷款还款、成绩分析、个人预算等案例一起,构成了一套比较完整的 KMP + ArkTS 教学示例,覆盖了健康、理财、学习等不同生活场景。你可以继续基于这个模式扩展更多功能,例如:

  • 为每一天添加日期信息,构建更直观的打卡日历;
  • 引入“番茄钟数量”“学习时长”等维度,打造更全面的学习统计仪表盘;
  • 将本地结果上传到后端,实现多设备同步和长期趋势分析。

无论后续如何扩展,这个案例都展示了一件核心事情:利用 KMP,把复杂的业务逻辑统一沉淀在 Kotlin 中,再通过 Kotlin/JS 把能力开放给 ArkTS(以及其它前端),从而显著提升跨端开发效率和可维护性。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐