【天体运行模拟|07】HarmonyOS ArkTS 公式页实战:呈现引力、圆周运动与单位说明
物理公式页看起来像一个简单列表,真正做起来却同时涉及三类准确性:公式本身要正确,符号与单位要让用户看懂,ArkUI 页面还要在手机、平板和 2in1 上保持可读。只把一串 Unicode 字符放进 Text,可以快速完成首版,却不等于已经建立了可验证的知识模型。
“天体运行模拟”的 FormulaPage.ets 提供了一个真实而清晰的起点:页面用 FormulaItem 描述名称、公式、说明和分类,内置万有引力、圆轨道速度、逃逸速度、动能、引力势能、质心和天文单位七条内容;顶部用横向滚动分类条切换“全部、引力、轨道、能量、尺度”,下方用 List 呈现公式卡片。
本文从这份源码出发,分析如何在 HarmonyOS 5.0 及以上版本中组织公式数据、符号、单位和响应式布局,并明确当前边界:现有页面是静态速查,不会计算结果;公式使用 Unicode 近似排版,没有 MathML 或专业公式渲染;多数条目没有完整单位说明。建议方案会与已实现能力严格区分。

唯一复核标记:FORMULA-ONE13-GRAVITY-UNITS-20260726:公式字符串负责速查,符号与单位模型负责解释,数值计算必须先完成量纲校验。
验证基线与真实数据
本文面向 HarmonyOS 5.0 及以上版本。实际工程应用版本为 1.0.0,targetSdkVersion 为 6.0.2(22),compatibleSdkVersion 为 6.0.1(21),入口模块覆盖 phone、tablet、2in1。本次只复核 FormulaPage.ets 及其真实路由入口,不把文章中的增强代码视为已上线功能。
源码静态统计如下:
| 项目 | 实际数量 |
|---|---|
| 公式条目 | 7 |
| 分类筛选项 | 5 |
| 引力类条目 | 2 |
| 轨道类条目 | 2 |
| 能量类条目 | 2 |
| 尺度类条目 | 1 |
| 数值计算入口 | 0 |
| 专业公式渲染组件 | 0 |
七条公式分别是 F = GMm / r²、v = √(GM / r)、vₑ = √(2GM / r)、Ek = ½mv²、Ep = -GMm / r、R = Σmᵢrᵢ / Σmᵢ 和 1 AU ≈ 1.496×10⁸ km。这些内容可以直接从 formulas 数组复核。
一、用 FormulaItem 统一卡片数据
当前模型只有四个字段:
interface FormulaItem {
name: string
formula: string
description: string
category: string
}
这种结构适合静态速查页。页面不需要为每条公式写独立组件,只需遍历数组:
ForEach(this.filteredFormulas(),
(item: FormulaItem) => {
ListItem() {
// render item
}
}
)
数据与布局分离后,添加公式只需新增对象,不必复制整段 ArkUI。它也让分类筛选可以复用同一字段。
不过,formula 只是显示字符串,description 只是自然语言。若未来要做单位解释、变量高亮或计算器,这四个字段还不够。
二、七条真实公式覆盖了什么
现有条目形成四组知识:
- 引力:万有引力、质心。
- 轨道:圆轨道速度、逃逸速度。
- 能量:动能、引力势能。
- 尺度:天文单位。
这不是百科全书式堆砌,而是围绕模拟页的质量、速度、距离和轨迹形成最小知识闭环。例如圆轨道速度和逃逸速度共享 GM/r,但前者对应闭合圆轨道条件,后者对应总机械能不小于零的临界条件。
公式页标题使用“引力速查”,因此范围控制是合理的。若继续扩展,应优先补充轨道周期、角动量和开普勒第三定律,而不是随意加入无关公式。
三、万有引力公式要同时解释变量与方向
页面当前显示:
F = GMm / r²
它给出的是力的大小。变量含义应明确:
| 符号 | 含义 | SI 单位 |
|---|---|---|
F |
两个天体间引力大小 | N |
G |
万有引力常量 | m³/(kg·s²) |
M、m |
两个天体质量 | kg |
r |
两质心距离 | m |
页面说明“随距离平方减小”容易产生歧义,更准确的表达是“力的大小与距离平方成反比”。同时还要强调方向沿两天体质心连线,彼此相反。
如果把它用于模拟计算,不能只计算标量:
const dx = x2 - x1
const dy = y2 - y1
const r2 = dx * dx + dy * dy
const r = Math.sqrt(r2)
const force = G * m1 * m2 / r2
const fx = force * dx / r
const fy = force * dy / r
还必须设置最小距离或软化参数,避免 r = 0 导致除零。当前公式页不执行这段计算,文章示例只用于说明公式到向量实现的差异。
四、圆轨道速度隐含了适用条件
页面显示:
v = √(GM / r)
这个式子来自向心力与万有引力平衡:
mv² / r = GMm / r²
约去测试天体质量 m 后得到圆轨道速度。它隐含:
- 中心天体质量远大于绕行天体。
- 轨道近似圆形。
- 只考虑两体引力。
- 速度方向与半径方向垂直。
若用户在三体系统中直接套用它,结果只是一组近似初值。公式页最好显示“适用条件”,避免公式看起来像任何场景都成立的通用按钮。
五、逃逸速度与圆轨道速度的关系
现有逃逸速度为:
vₑ = √(2GM / r)
在相同 M 与 r 下:
vₑ = √2 · v圆
这个关系非常适合做同卡片关联。用户看到圆轨道速度后,可以直接理解:
- 速度低于圆轨道速度不一定立即坠落,可能形成椭圆轨道。
- 等于圆轨道速度且方向正确时形成圆轨道。
- 高于圆轨道速度但低于逃逸速度时通常仍受束缚。
- 达到逃逸速度是理想两体模型下离开束缚的临界条件。
现有描述“达到该速度后可离开引力束缚”是合适的速查表述,但计算功能还应说明不考虑大气阻力、其他天体和自转。
六、能量公式需要统一符号
源码中动能写作:
Ek = ½mv²
引力势能写作:
Ep = -GMm / r
Unicode 形式在普通 Text 中可读,但下标并不统一:Ek 与 Ep 是普通字母,逃逸速度却使用 vₑ。建议统一为:
Eₖ = ½mv²
Eₚ = -GMm / r
或者统一使用 ASCII 形式,避免部分字体缺字:
E_k = 1/2 mv^2
E_p = -GMm/r
选择哪一种取决于目标字体与设备实测。不要在同一页面混用多套下标规则。
七、负引力势能不是“负能量错误”
Ep = -GMm/r 的负号来自把无穷远处势能定义为零。天体越靠近,势能越低,也就是数值更负。
对两体近似,总机械能为:
E = ½mv² - GMm/r
当 E < 0 时通常是束缚轨道;E = 0 对应抛物线逃逸临界;E > 0 对应非束缚轨道。这个解释可以把“能量”分类和“轨道”分类连接起来。
页面当前每条卡片只有一句说明。若内容继续增长,建议增加折叠详情,不要把所有推导都堆在列表首屏。
八、质心公式中的 R 是向量
源码显示:
R = Σmᵢrᵢ / Σmᵢ
其中 rᵢ 表示第 i 个天体的位置向量,因此 R 也是向量。在二维模拟里可以分别计算:
interface Point {
x: number
y: number
}
function centerOfMass(
bodies: BodyMassPoint[]
): Point {
let totalMass = 0
let weightedX = 0
let weightedY = 0
bodies.forEach((body: BodyMassPoint) => {
totalMass += body.mass
weightedX += body.mass * body.x
weightedY += body.mass * body.y
})
if (totalMass <= 0) {
return { x: 0, y: 0 }
}
return {
x: weightedX / totalMass,
y: weightedY / totalMass
}
}
若只写“共同质心旋转”,用户可能不知道它是可计算的位置。变量说明应标记 rᵢ 和 R 为位置向量。
九、天文单位是尺度换算,不是动力学公式
现有尺度条目:
1 AU ≈ 1.496×10⁸ km
它用于描述太阳系距离。更精确的定义值是:
1 AU = 149 597 870.7 km
速查页使用近似值没有问题,但要在模型中标记精度。若计算器输入 AU,再输出 km 或 m,应使用统一常量:
const AU_IN_METERS: number = 149_597_870_700
显示层可以舍入,计算层不要从显示字符串 1.496×10⁸ km 反向解析数值。

十、分类筛选是一个纯派生状态
源码只保存 selectedCategory,列表通过函数派生:
private filteredFormulas(): FormulaItem[] {
const cat =
this.categories[this.selectedCategory]
if (cat === '全部') {
return this.formulas
}
return this.formulas.filter(
f => f.category === cat
)
}
这种方式不会维护第二份过滤数组,避免分类变化后手工同步。七条数据规模下,每次过滤开销可以忽略。
风险在于分类仍是普通字符串。如果把某个条目误写成“轨道学”,编译器不会报错,该条目只会在“全部”中出现。可以收紧为联合类型:
type FormulaCategory =
| '引力'
| '轨道'
| '能量'
| '尺度'
interface FormulaItem {
name: string
formula: string
description: string
category: FormulaCategory
}
十一、横向滚动分类条适合窄屏
分类条放在:
Scroll() {
Row({ space: 8 }) {
// categories
}
}
.scrollable(ScrollDirection.Horizontal)
.scrollBar(BarState.Off)
这样手机窄屏不必压缩五个标签。被选项使用主色背景,未选项使用卡片背景和分隔线,同时保留文字颜色差异。
不过,关闭滚动条后,用户不一定知道右侧还有内容。当前只有五项,通常能通过首屏露出下一项形成提示;未来分类增加时,可以让末尾保留部分可见宽度,或在平板上改为不滚动的 Flex 布局。
十二、公式卡片的视觉层级
每张卡片依次显示:
- 名称。
- 21vp 粗体公式。
- 说明。
- 分类标签。
公式使用主色,名称使用正文色,说明使用次级色,阅读顺序明确。List({ space: 10 }) 负责纵向虚拟化和滚动,适合公式继续增加后的性能。
卡片内部使用固定 padding(16),边框与背景来自主题常量。圆角使用 Constants.SMALL_RADIUS,避免每个页面自行定义。
十三、Unicode 公式的优势与边界
Text('F = GMm / r²') 的优势是:
- 无额外依赖。
- 与 ArkUI 字体、颜色和无障碍树自然集成。
- 加载快,离线可用。
- 适合一行短公式。
边界也很明确:
- 复杂分式不能垂直排版。
- 多层上下标可读性下降。
- 根号覆盖范围只能靠括号表达。
- 字体可能缺少部分数学字符。
- 无法对单个变量单独着色或点击解释。
对于当前七条短公式,Unicode 是保守而有效的选择。只有当产品需要矩阵、积分、长分式或交互变量时,才值得引入更专业的排版方案。
十四、把符号说明做成结构化模型
建议扩展模型:
interface FormulaSymbol {
symbol: string
meaning: string
unit: string
}
interface FormulaItem {
id: string
name: string
formula: string
description: string
category: FormulaCategory
symbols: FormulaSymbol[]
assumptions: string[]
}
万有引力条目可定义:
{
id: 'universal_gravity',
name: '万有引力',
formula: 'F = GMm / r²',
category: '引力',
symbols: [
{ symbol: 'F', meaning: '引力大小', unit: 'N' },
{ symbol: 'M', meaning: '中心天体质量', unit: 'kg' },
{ symbol: 'm', meaning: '测试天体质量', unit: 'kg' },
{ symbol: 'r', meaning: '质心距离', unit: 'm' }
],
assumptions: ['点质量近似', '仅考虑两体作用']
}
这样详情页、无障碍朗读和计算器可以共享同一份语义,不必从公式字符串猜变量。
十五、数值计算前先定义单位策略
当前模拟页使用 M、px、v 等教学单位,公式页展示 SI 物理公式和 AU。二者不能直接代入,除非建立尺度映射。
推荐明确两层:
interface DisplayQuantity {
value: number
unit: 'M' | 'px' | 'v' | 'AU' | 'km'
}
interface SIQuantity {
value: number
unit: 'kg' | 'm' | 'm/s' | 'N' | 'J'
}
教学模拟可以继续使用归一化单位,但公式计算器必须告诉用户:
- 当前输入是现实 SI 单位还是模拟单位。
G使用真实常量还是归一化常量。- 距离
px如何映射到m。 - 输出是否只用于趋势演示。
如果这些规则没有定义,就不应添加“计算”按钮。
十六、量纲校验可以提前发现错误
以圆轨道速度为例:
[G] = m³·kg⁻¹·s⁻²
[M] = kg
[r] = m
因此:
[GM/r] = m²·s⁻²
√[GM/r] = m·s⁻¹
结果确实是速度单位。逃逸速度多了无量纲系数 2,量纲不变。
对于引力:
[GMm/r²] = kg·m·s⁻² = N
对每条公式保存 resultUnit 和输入单位,可以在开发测试中执行量纲断言。页面速查不必展示完整推导,但模型应能支持核验。
十七、页面状态还可以更完整
当前公式数据写在页面里,始终有内容,所以不需要加载状态。若将来从资源文件或数据库读取,应补齐:
loadingcontentemptyerror
即使保持静态数据,也应处理无匹配分类。当前分类数组与公式分类一致,不会出现该情况;类型收紧后,这个不变量更容易保持。
错误状态不应伪造成空状态。“暂无公式”和“公式数据加载失败”对用户是两件不同的事。
十八、多设备布局策略
源码根容器使用:
.width('100%')
.height('100%')
.padding({
top: this.statusBarHeight,
bottom: this.bottomBarHeight
})
手机上单列列表合理。平板与 2in1 可以根据可用宽度增强,而不改变数据模型:
- 小于 600vp:单列卡片。
- 600vp 到 960vp:两列公式网格或主列表加详情。
- 更宽窗口:左侧分类导航,右侧公式详情。
不要单纯把卡片拉到超宽。长行会降低说明文字可读性,可限制内容最大宽度并居中。
十九、状态栏、底部安全区与滚动
页面通过 @StorageProp 获取状态栏和底栏高度,根容器增加上下 padding。List 使用 layoutWeight(1) 占据剩余空间,顶部导航和分类条保持可见。
需要设备验证:
- 手势导航与三键导航。
- 横屏小窗口。
- 平板分屏。
- 2in1 窗口缩放。
- 系统字体放大。
底部 List 的 padding.bottom 当前为 12,再叠加根容器 bottomBarHeight。如果全局高度已包含系统避让,应避免重复留白;具体取决于入口处如何写入该状态值。

二十、无障碍不能只朗读视觉公式
屏幕阅读器直接朗读 v = √(GM / r) 可能不够自然。结构化符号模型可以提供更清晰的替代文本:
圆轨道速度等于万有引力常量乘中心天体质量除以轨道半径,再开平方。
分类标签需要足够触控面积和选中状态语义;公式卡片可以组合为一个可聚焦单元,也可以让名称与符号说明分别聚焦,但不要让装饰性分类标签重复朗读。
颜色不能成为唯一选中提示。当前选中项同时改变背景、文字和边框,方向是正确的,还应在真机无障碍服务下核验。
二十一、可执行的测试清单
| 测试 | 当前源码预期 |
|---|---|
| 初始进入 | 展示全部 7 条 |
| 选择“引力” | 展示万有引力、质心,共 2 条 |
| 选择“轨道” | 展示圆轨道速度、逃逸速度,共 2 条 |
| 选择“能量” | 展示动能、引力势能,共 2 条 |
| 选择“尺度” | 只展示天文单位,共 1 条 |
| 点击返回 | 调用 router.back() |
| 横向窄屏 | 分类条可滚动 |
| 纵向长内容 | List 可滚动且卡片不重叠 |
增强模型上线后还要增加:
- 每个公式 ID 唯一。
- 每个分类属于联合类型。
- 每个变量具有含义和单位。
- 结果单位与量纲断言一致。
- Unicode 字符在目标字体中可显示。
- 大字体下公式与说明不截断。
二十二、发布前公式页检查
- 公式符号与说明一致。
r明确表示质心距离。- 圆轨道速度标出两体与圆轨道近似。
- 逃逸速度说明理想模型边界。
- 势能负号有解释。
- 质心位置标明向量含义。
- AU 的近似精度有标识。
- 模拟单位与 SI 单位不混用。
- 分类字符串由类型约束。
- 字体放大后公式不溢出。
- 手机、平板、2in1 保持合适行宽。
- 状态栏、底部导航和返回操作可用。
二十三、总结
当前 FormulaPage.ets 用很少的代码完成了可用的离线速查:七条真实公式由统一模型驱动,五个分类通过纯派生函数过滤,横向 Scroll 保护窄屏分类条,List 负责公式卡片的纵向呈现。对于短公式,原生 Text 加 Unicode 是低成本且稳定的实现。
继续演进时,重点不应只是增加公式数量,而是补上符号、单位、适用条件、量纲和无障碍描述。显示字符串服务于阅读,结构化知识模型服务于验证与计算;在单位映射没有确定之前,宁可保持诚实的“速查页”,也不要提供看似精确但量纲不明的计算结果。
说明:本文基于真实 HarmonyOS/ArkTS 源码进行整理,部分文字与示例由 AI 辅助生成;所有现有能力与建议改造已明确区分。
更多推荐




所有评论(0)