
看到这个标题可能有人会觉得一个 BMI 计算器有什么好写的不就是两个输入框加一个除法。但真动手在 OpenHarmony 上把这件事做顺你会发现难点根本不在数学公式而在状态管理、单位换算和边界值处理。我最近就用 ArkTS 做了这么一个小应用OpenHarmony 简易 BMI 健康指数计算器把身高体重填进去一键算出 BMI 值给出分类判断和健康建议全程离线可用。这篇文章就把整个项目从思路、布局、计算逻辑到踩过的坑完整过一遍适合刚开始接触 OpenHarmony 应用开发、想用一个小工具练手 ArkUI 的朋友。1. 项目拆解别急着写代码先想清楚 BMI 计算器到底要做什么1.1 一次健康自评背后的真实需求很多人对 BMI 的计算停留在“体重除以身高的平方”但真要把这个功能做成一个能让普通用户正常使用的应用需求远比这句公式复杂。你得先回答几个问题身高输入的是厘米还是米体重输入的是千克还是斤结果怎么显示才能让人一眼看懂要不要给健康建议这个项目的核心场景是“健康自评”。用户打开应用填两个数拿到一个结果并且希望这个结果是能读懂的。也就是说BMI 计算器不只是算数它要完成“数据输入、单位换算、公式计算、结果分类、建议输出”这样一条完整链路。OpenHarmony 上这类轻量工具应用很适合作为入门项目界面简单、逻辑独立、不依赖复杂网络能力跑在模拟器上就能完整体验。我最初的想法更简单以为只要写一个函数就行。实际搭完页面才发现一个合格的自评工具至少要考虑三件事输入要防呆算出来要准结果要人话。所谓“防呆”就是用户填了空值、填了负数、填了明显不合理的身高体重应用不能直接崩溃更不能给出一个荒谬的 BMI。所谓“要准”指的是单位换算统一后再用标准公式计算。所谓“人话”就是不能只显示一个数字要告诉用户偏瘦、正常、超重还是肥胖并给出对应的日常建议。1.2 为什么选 OpenHarmony 和 ArkTS 做这种小工具选 OpenHarmony 做 BMI 计算器不是因为大而全的生态恰恰是因为它足够贴近“轻量终端”的场景。健康自评这类功能未来完全可能出现在智能手表、平板、带屏设备上。OpenHarmony 的 ArkUI 声明式写法写一次界面代码后续迁移到不同形态设备时不需要重写整套 UI 逻辑这一点对工具类应用很友好。ArkTS 的语言风格本身也适合这个项目。它基于 TypeScript 语法要求类型清晰禁止很多不规范的隐式转换。对这种小型计算逻辑来说类型明确反而是一种保护。我在项目里用到了State装饰器管理输入框的值用到parseFloat解析数字用到条件判断做分类用到的都是非常基础的能力却能把完整交互撑起来。换句话说这个项目不需要碰网络请求、数据库、多线程这些重东西它天然适合验证“从零到一”的 OpenHarmony 应用开发流程。如果你以前写过 Android 的 XML 布局再来看 ArkUI 会有一种很直接的感受界面是写出来的不是拉出来的。Column、Row、Text、TextInput、Button这些组件组合方式跟搭积木一样去掉了很多繁琐的布局嵌套。我建议新手从这种单一页面的计算器开始而不是一上来就做多页面、列表、路由跳转因为后者很容易让你在框架 API 里迷失方向。1.3 BMI 计算规则这不是数学题是交互逻辑BMI 的标准公式是体重千克除以身高米的平方。这个公式本身属于小学算术范围但放到真实场景里就会衍生出一堆细节。用户输入习惯五花八门身高大概率写厘米比如 175体重可能写斤比如 130性别、年龄也会影响健康评价但基础版项目里不建议一开始就做复杂个性化。我做的是中国成人参考标准同时也保留了国际通用标准的扩展空间。成年人 BMI 分类大概分成四档偏瘦、正常、超重、肥胖具体边界值如下表BMI 范围分类日常建议方向小于 18.5偏瘦关注营养摄入规律作息18.5 ~ 23.9正常保持当前生活方式24.0 ~ 27.9超重控制高热量饮食增加活动量28.0 及以上肥胖建议咨询专业人员制定管理方案注意BMI 是一个健康自评参考指标不构成医疗诊断。肌肉量高的人群、孕妇、未成年人等特殊情况下BMI 的参考意义有限。我在实现分类判断时用的是分段式条件判断而不是在界面上写死一段文字。这样后续如果要切换成不同地区的标准只需要改阈值常量。把业务规则和界面显示拆开算是这个小项目里最有价值的工程习惯之一。2. 环境搭建与页面骨架十分钟跑通一个 OpenHarmony 空白应用2.1 开发工具能跑起来就不容易了OpenHarmony 应用开发目前最常见的是通过 DevEco Studio 来完成工程创建。安装这块没什么捷径把开发工具、SDK、模拟器配齐就是最花时间的环节。版本对齐特别重要不同版本的工具链对应不同的 API Level如果创建工程时选择的 SDK 版本和本地模拟器不匹配后面编译会冒出各种奇怪的报错。新建项目时我建议选Empty Ability模板工程语言选择 ArkTS模型选 Stage 模型。Stage 模型是当前主流应用入口、页面管理、权限配置都更规范。包名可以写成一个示例域名工程创建完成后会自动生成默认的EntryAbility入口和pages/Index.ets页面我们只需要在这个页面里改造内容。有个小建议工程创建后先不要急着写代码先跑一次空白模板确认模拟器能启动、应用能安装。这一步看起来多余但它能帮你把“环境问题”和“业务代码问题”区分开。我见过很多新人改了页面代码后分不清是编译报错还是模拟器问题最后越调越乱。先把基线跑通后面写任何代码心里都有底。2.2 页面结构先画一个布局草图BMI 计算器的界面不需要花哨三块内容就够了标题区、输入区、结果展示区。用 ArkUI 的布局组件来组合最外层是一个垂直方向的Column内部放两个输入模块每个输入模块由一行数值输入框和单位切换按钮组成最下方放一个计算按钮和一个结果卡片。我当时先画了一个简单的布局草图顶部大标题“BMI 健康指数计算器”身高区输入框 单位切换按钮cm / 米体重区输入框 单位切换按钮kg / 斤计算按钮“开始计算”结果区BMI 数值、分类结果、健康建议对应到 ArkUI 代码大概是这样Entry Component struct BmiPage { State heightInput: string State weightInput: string State heightUnit: string cm State weightUnit: string kg State bmiResult: string -- State category: string State advice: string build() { Column({ space: 16 }) { Text(BMI 健康指数计算器) .fontSize(24) .fontWeight(FontWeight.Bold) Text(身高) .fontSize(16) Row({ space: 8 }) { TextInput({ placeholder: 请输入身高, text: this.heightInput }) .layoutWeight(1) .type(InputType.NUMBER) .onChange((value: string) { this.heightInput value }) Button(this.heightUnit cm ? cm : 米) .onClick(() { this.heightUnit this.heightUnit cm ? m : cm this.heightInput this.clearResult() }) } Text(体重) .fontSize(16) Row({ space: 8 }) { TextInput({ placeholder: 请输入体重, text: this.weightInput }) .layoutWeight(1) .type(InputType.NUMBER) .onChange((value: string) { this.weightInput value }) Button(this.weightUnit kg ? kg : 斤) .onClick(() { this.weightUnit this.weightUnit kg ? 斤 : kg this.weightInput this.clearResult() }) } Button(开始计算) .width(100%) .onClick(() { this.calculateBmi() }) Column({ space: 8 }) { Text(BMI${this.bmiResult}) .fontSize(32) .fontWeight(FontWeight.Bold) Text(this.category) .fontSize(20) Text(this.advice) .fontSize(16) } .width(100%) .padding(16) .backgroundColor(#f5f5f5) .borderRadius(12) } .width(100%) .padding(16) } }这段代码的阅读思路是界面从上到下依次堆叠Row解决输入框和单位按钮的横向排列layoutWeight(1)让输入框自动撑满剩余宽度.type(InputType.NUMBER)让弹出来的是数字键盘。布局本身不复杂但它把 ArkUI 最基础的组件用了一遍后续要扩展界面也是在这个基础上加东西。2.3 运行到模拟器和真机的差别跑模拟器时要注意默认模拟器对中文输入法的支持不一定完整但 BMI 计算器只需要数字输入用数字键盘类型影响不大。预览器其实就是个快速看布局的工具它适合静态检查 UI不适合验证完整交互。我习惯把“快速 layout 检查”和“真机/模拟器运行验证”分开这样定位问题更快。单位切换按钮触发后清空输入框是我在这个项目里特意加的。原因很简单身高 175 在厘米单位下是合理值如果切到米单位175 就成了一个离谱的数值不清空会让用户在不知情的情况下算出一个荒谬结果。这个设计虽然简单但很影响体验。3. 核心逻辑实现状态管理、单位换算与基础数学3.1 双向绑定与输入处理State 是 ArkTS 的“状态内存”ArkUI 声明式开发的核心思路是“数据驱动界面”。页面里用State修饰的变量一旦值发生变化UI 会自动重新渲染。对这个项目来说heightInput、weightInput、bmiResult这些变量全部用State管理这样输入框每敲一个字符状态就更新一次。TextInput的onChange回调会持续触发我们在回调里把最新字符串存进状态变量。这里有个关键点输入框的text参数也绑定了同一个状态变量看起来像双向绑定。但如果你在onChange里去修改输入值可能会出现光标跳动或者用户输入被覆盖的情况。所以在这个项目里我坚持“只存不改”onChange只是把输入值存起来真正的计算发生在点击按钮之后。之所以不在输入过程中实时计算是为了避免中间状态干扰体验。比如用户先填身高再填体重中间过程数值不完整实时算出来的结果也没意义。点击按钮再计算逻辑更清晰也方便将来扩展“实时计算”的开关。3.2 单位统一身高厘米转米体重斤转千克基础数学在这里体现得最明显。所有计算开始之前必须先把用户输入的数据换算成标准单位身高统一成米体重统一成千克。否则公式用错单位结果会错得非常离谱。具体换算规则是如果身高单位是 cm先除以 100 转成米如果已经是米就直接使用。如果体重单位是斤先除以 2 转成千克如果已经是千克就直接使用。换算逻辑写成代码function parseHeight(value: string, unit: string): number { let height Number.parseFloat(value) if (unit cm) { return height / 100 } return height } function parseWeight(value: string, unit: string): number { let weight Number.parseFloat(value) if (unit 斤) { return weight / 2 } return weight }这样处理之后后续计算只需要一行公式let bmi weightKg / (heightM * heightM)可能有人会问怎么不用Math.pow(heightM, 2)两种写法都可以heightM * heightM在数值简单时更直观可读性也更好。对于 BMI 这个场景平方运算用乘法就够了没必要引入函数调用。3.3 校验逻辑程序防呆是体验的一部分校验是这类小工具最容易忽略但最不能忽略的部分。直接在计算函数开头堆if判断看起来很简单但对用户来说这些判断决定了工具是否“靠谱”。我在项目里处理了四类非法输入输入为空提示“请完整填写身高和体重”。解析结果为非数字提示“请输入有效数字”。数值小于等于 0提示“身高或体重不能为 0 或负数”。数值明显异常例如身高小于 20 或大于 250 厘米、体重小于 5 或大于 300 千克直接说明合理范围。加范围校验是我在实际使用后补上的。第一次做完只做了空值和正负判断然后我发现填“身高 1 厘米、体重 1 千克”也能算出一个看似正常的结果但没有任何实际意义。后来就加上了合理范围判断并且在提示里写出可参考的范围而不是干巴巴地说“输入非法”。完整计算逻辑大致是这样calculateBmi() { let heightM this.parseHeight(this.heightInput, this.heightUnit) let weightKg this.parseWeight(this.weightInput, this.weightUnit) if (this.heightInput || this.weightInput ) { this.category 请完整填写身高和体重 this.advice return } if (isNaN(heightM) || isNaN(weightKg)) { this.category 请输入有效数字 this.advice return } if (heightM 0 || weightKg 0) { this.category 身高或体重不能为 0 或负数 this.advice return } let bmi weightKg / (heightM * heightM) let roundedBmi Math.round(bmi * 10) / 10 this.bmiResult roundedBmi.toFixed(1) this.updateCategory(roundedBmi) }保留一位小数时我用的是Math.round(bmi * 10) / 10而不是直接toFixed(1)。原因是toFixed存在四舍五入的不确定性先通过Math.round得到数值结果再转字符串展示展示层就稳定很多。3.4 结果分类与健康建议边界值怎么归属分类判断的代码并不复杂难在各档位的边界归属。18.5、23.9、24、28 这些临界值在判断时一定要用一致的比较方向。我写的是“上一档的下限包含下一档的上限左开”这样每个数值只会命中一个分类。updateCategory(bmi: number) { if (bmi 18.5) { this.category 偏瘦 this.advice 建议保证均衡营养规律作息适当增加蛋白质摄入。 } else if (bmi 18.5 bmi 24) { this.category 正常 this.advice 身体质量指数处于正常范围继续保持良好生活习惯。 } else if (bmi 24 bmi 28) { this.category 超重 this.advice 建议控制高热量食物增加日常活动量。 } else { this.category 肥胖 this.advice 建议制定科学的饮食与运动计划必要时咨询专业人员。 } }这里的bmi 18.5 bmi 24和表格里的 18.5 ~ 23.9 是一致的只是代码里用了 24 作为开区间上界。如果将来参考标准调整只需要改阈值不用动整体逻辑。这也是我在前面强调的“规则与界面分离”在这个项目里的实际落地。建议文案我尽量写得口语化、可操作但不会写具体药方和运动计划一方面是项目定位就是自评参考另一方面也避免把工具做成“医疗建议生成器”这是内容和定位双重安全考虑。4. 实操过程与避坑记录这类小工具最容易翻车的点4.1 输入框键盘和单位切换的兼容问题项目里我直接给TextInput设置了数字键盘实际跑起来发现不同模拟器对数字键盘的表现不一样。有的环境里InputType.NUMBER能弹出数字键盘有的只弹出常规键盘。这不是我们代码能完全控制的但可以在输入框上再加一层“只允许数字和小数点”的字符过滤。单位切换按钮在 ArkUI 里我是用普通Button实现的每次点击切换状态并清空输入值。这样做的好处是逻辑简单坏处是按钮的选中态不够明显。如果做完这个版本还想优化可以考虑引入Radio组件用单选组来表达当前单位。但基础版不建议投入太多精力在控件外观上先把状态切换逻辑跑通更重要。我遇到的另一个问题是切换单位后之前的 BMI 结果还显示在界面上。用户可能误以为这是新单位下的结果。于是我每次切换单位都调用clearResult()把结果归位为初始状态。这种细节不写代码的时候很容易漏但直接影响用户对数据的信任。4.2 浮点精度和极端数据的处理用浮点数做四则运算迟早会遇到精度问题。BMI 计算虽然只是除法但在某些数值组合上结果会出现一长串小数位。我在结果展示前做了“四舍五入保留一位小数”然后再显示避免出现“24.999999999”这种让用户困惑的内容。极端数据也要限制。比如用户输入身高 2.5 米、体重 200 千克算出来的 BMI 是 32看起来合理但实际场景里这类数据还有待商榷。我加了一个合理范围判断但不把范围限制得太死毕竟不同人的体型差异很大。如果输入超出明显物理可能比如身高 10 米、体重 1 千克我会直接拦截而不是参与计算。拦截时机放在单位换算之后、BMI 计算之前。因为单位换算后的数据才是统一标准下的数值比如身高按厘米时 175 在范围内按米时 175 就是超出范围了。这也是单位切换后清空输入框的另一个理由避免用户沿用旧数值触发限值提示。4.3 软键盘遮挡与布局重排模拟器上测试时点击输入框弹出软键盘结果区可能被顶上去或者被遮挡。OpenHarmony 侧处理软键盘有一个基本思路就是让页面整体能够滚动或避让。我这个项目页面上内容不多但手机上屏幕高度有限键盘弹起后结果区确实可能被挡住。简单处理方法是把最外层Column放进Scroll或者在页面配置里允许键盘避让。我选了Scroll方案因为键盘避让在不同版本上的默认行为不一致Scroll至少能保证用户滚动后看到底部内容。这里唯一的坑是Scroll和Column的嵌套写法要先理清楚Scroll里放一个Column再在Column里排内容而不是让Scroll直接管理多个子组件。4.4 常见问题速查表把这次项目中可能遇到的问题整理成一个速查表以后有人在这个项目基础上二次开发排查效率会高很多。现象可能原因解决方式输入框弹出全键盘而不是数字键盘部分环境对 InputType.NUMBER 支持不完整在 onChange 里过滤非数字字符计算结果小数位很多浮点数除法精度问题用 Math.round(bmi * 10) / 10 保留一位小数单位切换后旧结果未清空状态变量没有重置统一调用 clearResult() 清空结果区输入 0 或负数没有提示缺少校验逻辑在计算前增加空值、正负、范围判断键盘弹出后按钮被遮挡页面未做滚动或避让最外层用 Scroll 包裹 Column4.5 我踩过的一个印象深刻的坑这个项目里最让我印象深刻的问题是“计算结果正确但建议文案却不匹配”。排查了很久才发现BMI 值和分类文字是分开的两个状态变量计算函数里更新了bmiResult但分类建议在另一个函数里异步更新顺序没控制好界面已经刷新了分类还是旧值。后来我把计算和分类放在同一个同步流程里在一个calculateBmi()方法里依次完成所有状态更新问题就没了。这个教训很基础但很值得记下来一个页面的多个状态变量更新要尽量保持原子性不让 UI 有机会渲染“中间状态”。尤其是这种几行代码就能完成的小计算逻辑写成一个大流程反而比拆成多个异步函数更稳。5. 从 BMI 计算器到健康自评可以继续做的方向5.1 如果让我重新做一遍我会怎么调整第一件事我会把状态变量整理成一个数据模型。现在项目里heightInput、weightInput、heightUnit、weightUnit是四个分散的State字符串代码量小的时候没什么感觉但等我要增加性别、年龄、记录历史这些功能时分散管理就会变得混乱。定义成一个HealthMetricModel之类的对象整体状态更新更清晰。第二件事我会把健康建议文案抽到单独的文件里而不是写死在页面里。这个项目里建议就几行字以后想根据性别、年龄段给出不同建议如果还堆在updateCategory里会非常长。拆出去之后维护文案的人可以完全不碰代码。第三件事我会增加“历史记录”和“趋势图”这两个扩展点。健康自评不应该是算完就扔如果能把每次计算结果本地存下来用一张简单的折线图展示 BMI 变化趋势这个应用就从一个一次性计算工具变成了一个轻量健康管理工具。OpenHarmony 的 Canvas 组件可以实现简单的趋势图不需要引入第三方图表库。5.2 我在这个项目里学到的核心一课这个小项目让我对 ArkTS 的最大感受不是它有多强大的框架能力而是它把“界面状态”和“业务计算”的边界逼得非常清楚。用State管理视图数据用纯函数处理单位换算和 BMI 计算页面组件只负责展示和用户交互数据逻辑只负责算数和校验整个代码结构不需要复杂架构就能保持可读。如果你也打算用 OpenHarmony 练手我建议就从这种“单页、单功能、纯本地”的工具开始。它不需要网络、不需要数据库、不需要多页面跳转却能把 ArkUI 最核心的状态管理、组件组合、事件回调全部覆盖一遍。做完以后再往里面加特性就会顺很多。现在你拿到这套代码和思路完全可以自己动手复刻一版。先确认开发环境能跑通再照着页面骨架把界面搭起来然后实现校验与计算逻辑最后自己多测几组边界数据。踩坑的过程本身就是这个项目最有价值的部分。