鸿蒙 ArkTS 实战:Split Bill Calculator 从AA账单到账单分摊应用完整解析
鸿蒙 ArkTS 实战:Split Bill Calculator 从AA账单到账单分摊应用完整解析
前言
AA账单 是一个偏工具属性的鸿蒙 ArkTS 单页应用。它围绕“记录聚餐总额、人数和服务费比例,自动计算含服务费总计以及每人应付金额。”这个具体需求,把数值状态、滑杆输入、公式计算和即时反馈组合在同一个页面中。
本文基于 Split Bill Calculator 的真实源码展开,重点分析它如何用 @State、Slider、Button、ForEach、条件样式和计算方法完成一个可用的小工具页面。
工具类应用最怕结果不透明。输入变化后,计算结果应该立刻出现在用户眼前。

图示说明:本文按照输入区、计算区、结果区和操作区拆解页面,帮助读者把 ArkTS 声明式 UI 与实际业务公式联系起来。
一、项目背景与目标
1.1 业务场景
记录聚餐总额、人数和服务费比例,自动计算含服务费总计以及每人应付金额。
这个场景的关键不是页面跳转,而是输入参数变化后结果同步变化。因此,状态字段的设计和公式表达比复杂导航更重要。
1.2 用户路径
- 打开页面查看默认参数。
- 通过滑杆、按钮或列表调整输入。
- 页面立即刷新计算结果或状态文案。
- 用户根据结果完成记录、估算、确认或对比。
1.3 技术目标
| 目标 | 页面体现 | 技术实现 |
|---|---|---|
| 参数输入 | 滑杆、按钮、列表 | Slider / Button / ForEach |
| 状态保存 | 当前数值和选择 | @State |
| 公式计算 | 金额、进度、总量、时长 | 方法或表达式 |
| 结果反馈 | 数字、文案、颜色 | Text / 条件样式 |
二、页面入口与工程结构
2.1 入口组件
项目主要逻辑集中在 Index 页面中,用 @Entry 标记入口,用 @Component 声明组件。
@Entry
@Component
struct Index {
build() {
Column() {
Text('AA账单')
}
}
}
2.2 单页工具的特点
AA账单 没有复杂路由,页面围绕 amount、people、servicePercent 展开。这种结构非常适合讲清楚一个工具型页面的计算闭环。
2.3 页面区域拆分
| 区域 | 作用 | 常见组件 |
|---|---|---|
| 标题区 | 展示工具名称与当前摘要 | Row、Column、Text |
| 输入区 | 调整数值、时段或选项 | Slider、Button、ForEach |
| 结果区 | 显示计算结果 | Text、Stack、Circle |
| 说明区 | 补充规则和业务解释 | Text |
三、状态建模
3.1 状态字段表
| 字段 | 类型 | 含义 |
|---|---|---|
| amount | number | 账单总额,默认 268 |
| people | number | 分摊人数,默认 4 |
| servicePercent | number | 服务费比例,默认 10% |
这些字段都直接影响页面结果。例如数字字段会参与公式计算,布尔字段会控制确认状态,字符串字段会展示当前阶段或侧别。
3.2 状态声明代码
@Entry
@Component
struct Index {
@State amount: number = 268; // 账单总额,默认 268
@State people: number = 4; // 分摊人数,默认 4
@State servicePercent: number = 10; // 服务费比例,默认 10%
}
3.3 状态设计原则
AA账单 的状态设计遵循一个清晰原则:会变的用户输入放入 @State,不变的业务配置放入普通字段或数组。
对工具类页面而言,状态字段越清楚,计算公式越容易验证。
四、数据配置与业务选项
4.1 静态数据
无数组字段:页面以金额、人数、服务费三个滑动参数完成计算
如果页面存在列表、时段或项目选项,就使用数组集中管理;如果页面主要是滑杆参数,就直接围绕数值状态构建。
4.2 ForEach 渲染模式
ForEach(this.items, (item: string, index: number) => {
Text(item)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.selected = index
})
})
4.3 索引与详情绑定
Text(this.items[this.selected])
.backgroundColor(this.selected === index ? '#4F46E5' : '#FFFFFF')
这种方式适合固定选项场景,既能减少重复代码,也能让选中态与详情区保持一致。
五、输入控件设计
5.1 Slider 的使用
这批工具类页面大量使用 Slider,因为它适合调节金额、人数、时长、容量、比例等连续或阶梯数值。
Slider({ value: this.value, min: 1, max: 100, step: 1 })
.onChange((value: number) => {
this.value = Math.round(value)
})
5.2 Button 的使用
Button 适合离散操作,例如增加一次、重置、确认、推进下一阶段。
Button('增加')
.backgroundColor('#4F46E5')
.onClick(() => {
this.count += 1
})
5.3 输入控件比较
| 控件 | 适用数据 | 项目价值 |
|---|---|---|
| Slider | 范围内数值 | 快速调节参数 |
| Button | 离散动作 | 增减、确认、重置 |
| ForEach | 固定选项 | 渲染列表或时段 |
| Toggle | 二值状态 | 在线、开关、确认 |
六、核心交互行为
6.1 主要动作
- +20 和 -20 按钮调整 amount
- 人数 Slider 调整 people
- 服务费 Slider 调整 servicePercent
- 每人应付金额实时刷新
6.2 源码关键片段
Text('¥' + String(Math.round((this.amount * (100 + this.servicePercent) / 100) / this.people)))
Slider({ value: this.people, min: 1, max: 12, step: 1 })
6.3 交互闭环
- +20 和 -20 按钮调整 amount
- 人数 Slider 调整 people
- 服务费 Slider 调整 servicePercent
- 每人应付金额实时刷新
这些动作的共同点是:用户一操作,状态立刻变化;状态一变化,计算结果或展示文案立刻刷新。
七、计算公式解析
7.1 核心公式
perPerson = Math.round((amount * (100 + servicePercent) / 100) / people)
7.2 公式放在哪里
当公式较短时,可以直接写在 Text 表达式里;当公式有多步计算时,更适合封装为方法。
private result(): number {
return Math.round(this.value * 100) / 100
}
7.3 结果显示
Text('结果:' + String(this.result()))
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor('#4F46E5')
公式结果一定要放在视觉重点区域,让用户看得见输入变化带来的影响。
八、ArkTS 组件细节
8.1 Text 数字展示
数字结果通常需要更大字号和更强对比度。
Text(String(total))
.fontSize(44)
.fontWeight(FontWeight.Bold)
8.2 Stack 结果区域
Stack 可以把圆形背景、数字和单位叠在一起,形成工具仪表盘效果。
Stack() {
Circle().width(190).height(190).fill('#F8FAFC')
Text('结果')
}
8.3 条件文案
条件文案可以把冷冰冰的数字转换成用户能理解的提示。
Text(this.done ? '目标已完成' : '继续记录')
8.4 条件颜色
.fontColor(this.warning ? '#DC2626' : '#4F46E5')
颜色要服务于状态变化,比如完成、风险、推荐、已预约等。
九、页面布局策略
9.1 左右结构
部分工具采用左侧摘要、右侧参数的结构,适合同时展示结果和输入控件。
Row() {
Column() { Text('结果') }.width('36%')
Column() { Text('参数') }.layoutWeight(1)
}
9.2 上下结构
上下结构适合先展示标题和结果,再展示滑杆与按钮。
Column() {
Header()
ResultPanel()
ControlPanel()
}
9.3 信息密度控制
| 信息 | 展示位置 | 原因 |
|---|---|---|
| 核心结果 | 居中或顶部 | 最先被看到 |
| 输入参数 | 中下部 | 方便调整 |
| 辅助说明 | 底部 | 不干扰操作 |
十、调试与验证
10.1 验证路径
- 调整每个 Slider,确认状态值变化。
- 点击按钮,确认边界判断生效。
- 检查公式结果是否随状态刷新。
- 检查条件文案和颜色是否符合当前状态。
10.2 日志辅助
Button('调试')
.onClick(() => {
console.info('value changed')
})
10.3 常见问题
| 问题 | 可能原因 | 处理方式 |
|---|---|---|
| Slider 后数字不变 | onChange 未写回 @State | 检查赋值语句 |
| 结果为小数过长 | 未做 Math.round | 增加格式化 |
| 按钮越界 | 缺少 if 判断 | 限制最小或最大值 |
| 选项错位 | index 与数组不一致 | 检查数据顺序 |
十一、可扩展能力
11.1 本地保存
AA账单 可以把当前参数保存到本地,下次打开页面时恢复上一次记录。
interface SavedToolState {
values: number[]
updatedAt: string
}
11.2 历史记录
工具类应用很适合增加历史记录,例如每天喝水量、每次喂养记录、每次估算方案或每次会议预约。
11.3 导出与分享
对于估算和排期类工具,可以把结果导出为文本或截图,便于发送给他人确认。
{
"tool": "AA账单",
"fields": "amount、people、servicePercent",
"result": "calculated"
}
十二、体验优化
12.1 参数范围
Slider 的 min、max、step 要贴近真实业务,范围太大或步长太细都会降低操作效率。
12.2 数值格式
金额、毫升、秒、小时、人数、度数都要带单位,避免用户误解。
12.3 反馈即时性
工具页面不要等提交后才显示结果,输入变化时就应该实时反馈。
十三、完整实现片段
13.1 状态骨架
@Entry
@Component
struct Index {
@State amount: number = 268; // 账单总额,默认 268
@State people: number = 4; // 分摊人数,默认 4
@State servicePercent: number = 10; // 服务费比例,默认 10%
}
13.2 业务配置
// 无数组字段:页面以金额、人数、服务费三个滑动参数完成计算
private primaryColor: string = '#4F46E5'
13.3 交互片段
Text('¥' + String(Math.round((this.amount * (100 + this.servicePercent) / 100) / this.people)))
Slider({ value: this.people, min: 1, max: 12, step: 1 })
13.4 结果渲染
Text('计算结果')
.fontSize(28)
.fontWeight(FontWeight.Bold)
.fontColor('#4F46E5')
十四、同类工具复用
14.1 复用状态模式
- number 字段用于金额、时间、容量、次数和索引。
- boolean 字段用于确认或开关。
- string 字段用于阶段、方向或分类。
- 方法用于封装较复杂的计算公式。
14.2 复用交互模式
AA账单 的写法可以迁移到预算工具、计时工具、排期工具、健康记录和家庭计算器中。
14.3 复用视觉模式
结果区使用大数字,输入区使用 Slider,操作区使用主题色按钮,这是工具类应用很稳妥的组合。
十五、学习价值
15.1 对初学者
这个项目适合练习 @State、Slider、Button、Text 表达式和计算方法。
15.2 对业务开发者
它展示了如何把真实生活中的小计算、小记录、小排期转换成可交互页面。
15.3 对工程扩展
后续可以加入本地缓存、历史列表、数据导出和多设备同步,让 AA账单 从单页工具扩展成完整应用。
总结
Split Bill Calculator 通过清晰的状态字段和计算逻辑完成了 账单分摊 场景。页面围绕 amount、people、servicePercent 管理输入状态,围绕 无数组字段:页面以金额、人数、服务费三个滑动参数完成计算 组织业务选项,再通过公式 perPerson = Math.round((amount * (100 + servicePercent) / 100) / people) 给出即时结果。
从这个项目可以看到,鸿蒙 ArkTS 非常适合构建轻量工具:状态清楚、输入直接、公式透明、结果即时。把这四点做好,一个小工具页面就能既好用又容易维护。
相关链接:
更多推荐



所有评论(0)