Flutter 实战:percentage_calculator 百分比计算器的三模式公式、实时计算与 鸿蒙 适配解析

前言

百分比计算器看起来只是一个小工具,但它覆盖了 Flutter 应用里非常典型的一组能力:输入解析模式切换状态刷新结果展示快捷样例跨端布局适配percentage_calculator 正是这样一个适合拆解的小型项目,它用一个页面完成三类常用计算:

  • 计算某个数值的百分之几。
  • 计算一个数值占另一个数值的百分比。
  • 计算新旧数值之间的百分比变化。

这篇文章会从真实 Flutter 实现出发,逐层拆解页面结构、状态字段、计算公式、UI 交互、测试思路与 OpenHarmony 适配观察。重点不是堆砌代码,而是把一个轻量工具应用怎样写得清晰、稳定、容易迁移讲完整。

小工具类应用的难点往往不在算法本身,而在“输入含义、公式分支、结果单位、异常边界”是否始终一致。

在这里插入图片描述

图示说明:上图用于展示 Flutter 页面布局在多端环境中的视觉组织方式。percentage_calculator 的核心界面由模式切换区、两个数字输入框、结果卡片和快捷示例组成。

一、项目定位与功能边界

1.1 应用定位

percentage_calculator 是一个基于 Flutter 和 Material 3 的百分比计算工具。它不是复杂业务系统,而是一个面向高频日常场景的单页应用。

典型使用场景包括:

  1. 购物折扣:计算 199 元的 20% 是多少。
  2. 成绩占比:计算 45 分占 60 分的百分比。
  3. 增长率:计算销售额从 50 增长到 60 后的变化率。
  4. 数据复核:通过快捷示例快速确认公式是否按预期工作。

1.2 功能清单

功能 页面表现 源码实现方式
三种计算模式 顶部三个模式标签 _selectedMode 控制当前公式
双输入框 Value、Percentage 或 New Value、Old Value 两个 TextEditingController
实时计算 输入变化后立即刷新结果 onChanged 调用 _calculate()
结果卡片 大字号展示计算结果 Card + Text
快捷示例 一键填充典型输入 ActionChip 写入控制器
Material 3 主题 蓝色种子色、现代控件风格 ThemeData(useMaterial3: true)

1.3 技术栈概览

技术点 项目用法 说明
Flutter 构建跨端 UI 页面、布局、输入、卡片均来自 Flutter Widget
Dart 编写状态与公式逻辑 使用类、方法、控制流和空安全语法
Material 3 统一视觉风格 使用 ColorScheme.fromSeed 配置主题
StatefulWidget 承载交互状态 输入值、模式和结果都在 State 内维护
flutter_test 可做 Widget 测试 验证页面渲染、点击和结果变化

二、工程结构与运行方式

2.1 目录结构

项目结构保持了 Flutter 默认应用的清晰形态,核心代码集中在 lib/main.dart

路径 作用
lib/main.dart 应用入口、页面状态、计算公式和 UI 构建
pubspec.yaml Flutter SDK 依赖、Material 图标声明
test/widget_test.dart Widget 自动化测试入口
ohos/ OpenHarmony 端工程目录
analysis_options.yaml Dart 静态分析规则

2.2 环境准备

运行 Flutter 项目前,可以先确认本地环境:

flutter doctor
flutter pub get
flutter run

这三个命令分别用于检查环境、拉取依赖和启动应用。由于当前项目没有引入复杂三方插件,依赖恢复通常比较直接。

2.3 依赖结构

percentage_calculator 的依赖非常轻,核心只依赖 Flutter SDK 和常见的开发测试工具。

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^5.0.0

这种依赖结构对跨端迁移很友好,因为业务逻辑、输入解析和界面渲染主要运行在 Dart 层,平台侧只需要关注窗口、输入法、字体和构建链路。

三、应用入口与根组件设计

3.1 main 函数

Flutter 应用从 main() 进入,然后通过 runApp 加载根组件。

import 'package:flutter/material.dart';

void main() {
  runApp(const PercentageCalculatorApp());
}

这段代码非常短,但它明确了应用启动的根节点:PercentageCalculatorApp

3.2 StatelessWidget 根组件

根组件不保存业务状态,只负责配置应用级信息。

class PercentageCalculatorApp extends StatelessWidget {
  const PercentageCalculatorApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Percentage Calculator',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const PercentageCalculatorHomePage(title: 'Percentage Calculator'),
    );
  }
}

这里有三个关键点:

  • MaterialApp 提供 Material 应用上下文。
  • ThemeData 统一颜色、组件和交互反馈。
  • home 指向真正承载业务的首页。

3.3 为什么根组件不使用 StatefulWidget

根组件只负责全局壳层,页面输入和结果变化都发生在首页内部。因此根组件使用 StatelessWidget 更合适,状态集中到 PercentageCalculatorHomePage 中,代码边界更清楚。

组件 是否持有状态 责任
PercentageCalculatorApp 应用标题、主题、首页
PercentageCalculatorHomePage 输入、选择、计算、渲染

四、页面状态设计

4.1 StatefulWidget 首页

首页使用 StatefulWidget,因为它必须响应用户输入并刷新页面。

class PercentageCalculatorHomePage extends StatefulWidget {
  const PercentageCalculatorHomePage({super.key, required this.title});
  final String title;

  
  State<PercentageCalculatorHomePage> createState() =>
      _PercentageCalculatorHomePageState();
}

title 作为不可变参数传入,真正会变化的数据放在 State 对象里。

4.2 State 中的核心字段

class _PercentageCalculatorHomePageState
    extends State<PercentageCalculatorHomePage> {
  final TextEditingController _valueController = TextEditingController();
  final TextEditingController _percentController = TextEditingController();
  double _result = 0;
  int _selectedMode = 0;
}

四个字段完整描述了页面的当前状态。

字段 类型 含义
_valueController TextEditingController 第一个输入框的文本控制器
_percentController TextEditingController 第二个输入框的文本控制器
_result double 当前公式计算出的结果
_selectedMode int 当前选中的计算模式

4.3 模式编号设计

项目用 int 表示三种模式:

模式编号 页面文案 公式含义
0 Calculate % 计算一个数的百分之几
1 X is % of Y 计算 X 占 Y 的百分比
2 % Change 计算新旧值变化率

这种写法简单直接,适合小型项目。后续如果模式继续增加,可以考虑把模式抽象成枚举,让语义更强。

五、三种百分比公式解析

5.1 计算某个数的百分之几

第一种模式对应“X% of Y”的问题。源码中第一个输入框表示数值,第二个输入框表示百分比。

_result = value * percent / 100;

例如输入:

Value Percentage Result
100 20 20.00
250 15 37.50
80 12.5 10.00

5.2 计算 X 占 Y 的百分比

第二种模式解决“X is what percent of Y”的问题。

_result = percent > 0 ? (value / percent) * 100 : 0;

这里的 percent 字段实际代表分母值。为了避免除数为 0,源码先判断 percent > 0,不满足时返回 0。

Value Base Result
15 50 30.00
45 60 75.00
2 8 25.00

5.3 计算百分比变化

第三种模式用于计算新旧值之间的变化率。

_result = percent > 0 ? ((value - percent) / percent) * 100 : 0;

在这个模式下,第一个输入框表示新值,第二个输入框表示旧值。

New Value Old Value Result
60 50 20.00%
80 100 -20.00%
120 80 50.00%

公式分支越少,越要把输入标签写准确。否则用户看到的结果可能正确,但理解会出现偏差。

六、核心计算方法拆解

6.1 输入解析

源码通过 double.tryParse 将输入框文本转换为数字。

final value = double.tryParse(_valueController.text) ?? 0;
final percent = double.tryParse(_percentController.text) ?? 0;

这种写法有两个优点:

  1. 空字符串不会导致异常。
  2. 非法数字会自动回退为 0。

6.2 switch 分支

三种计算模式都集中在 _calculate() 方法里。

void _calculate() {
  final value = double.tryParse(_valueController.text) ?? 0;
  final percent = double.tryParse(_percentController.text) ?? 0;

  setState(() {
    switch (_selectedMode) {
      case 0:
        _result = value * percent / 100;
        break;
      case 1:
        _result = percent > 0 ? (value / percent) * 100 : 0;
        break;
      case 2:
        _result = percent > 0 ? ((value - percent) / percent) * 100 : 0;
        break;
    }
  });
}

setState() 包住计算结果的写入,保证 _result 变化后界面立即重建。

6.3 计算方法的职责边界

_calculate() 只做三件事:

  • 读取两个输入框。
  • 根据模式执行公式。
  • 写入新的结果状态。

它不直接创建 Widget,也不处理样式。这种分层让代码更容易阅读,也方便后续为公式单独补充测试。

七、模式切换控件设计

7.1 顶部模式容器

页面顶部使用一个浅蓝色容器包住三个模式标签。

Container(
  decoration: BoxDecoration(
    color: Colors.blue.shade50,
    borderRadius: BorderRadius.circular(12),
  ),
  child: Row(
    children: [
      _buildModeTab(0, 'Calculate %', Icons.calculate),
      _buildModeTab(1, 'X is % of Y', Icons.percent),
      _buildModeTab(2, '% Change', Icons.trending_up),
    ],
  ),
)

这种结构让三个模式在视觉上形成一个整体,用户能快速理解它们是互斥选项。

7.2 单个模式标签

单个模式标签由 _buildModeTab 生成。

Widget _buildModeTab(int mode, String label, IconData icon) {
  final isSelected = _selectedMode == mode;
  return Expanded(
    child: GestureDetector(
      onTap: () => setState(() => _selectedMode = mode),
      child: Container(
        padding: const EdgeInsets.symmetric(vertical: 12),
        decoration: BoxDecoration(
          color: isSelected ? Colors.blue : Colors.transparent,
          borderRadius: BorderRadius.circular(12),
        ),
        child: Column(
          children: [
            Icon(icon),
            const SizedBox(height: 4),
            Text(label, textAlign: TextAlign.center),
          ],
        ),
      ),
    ),
  );
}

Expanded 保证三个标签均分宽度,GestureDetector 接收点击事件,isSelected 控制选中态样式。

7.3 选中态反馈

状态 背景 图标颜色 文本字重
未选中 透明 灰色 普通
已选中 蓝色 白色 加粗

清晰的选中态对计算器非常重要,因为公式切换会改变第二个输入框含义和最终结果含义。

八、输入框与动态标签

8.1 第一个输入框

第一个输入框在变化率模式下显示 New Value,其他模式显示 Value

TextField(
  controller: _valueController,
  keyboardType: TextInputType.number,
  style: const TextStyle(fontSize: 24),
  decoration: InputDecoration(
    labelText: _selectedMode == 2 ? 'New Value' : 'Value',
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
    filled: true,
  ),
  onChanged: (_) => _calculate(),
)

动态标签能降低用户理解成本,尤其是 % Change 模式下,新值和旧值不能混淆。

8.2 第二个输入框

第二个输入框在变化率模式下显示 Old Value,其他模式显示 Percentage

TextField(
  controller: _percentController,
  keyboardType: TextInputType.number,
  style: const TextStyle(fontSize: 24),
  decoration: InputDecoration(
    labelText: _selectedMode == 2 ? 'Old Value' : 'Percentage',
    suffixText: _selectedMode != 1 ? '%' : null,
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(12),
    ),
    filled: true,
  ),
  onChanged: (_) => _calculate(),
)

suffixText 只在部分模式展示百分号。第二种模式里第二个输入框表示基准值,不应该显示 %

8.3 输入行为表

模式 第一个输入框 第二个输入框 第二个输入框后缀
Calculate % Value Percentage %
X is % of Y Value Percentage
% Change New Value Old Value %

这里可以看到,源码已经根据模式动态调整标签和后缀,但第二种模式的 labelText 仍显示 Percentage。从产品语义看,它代表基准值;文章分析时要如实说明,避免夸大成完整的商业级表单设计。

九、结果卡片实现

9.1 Card 结构

结果区域使用 Card 加渐变背景突出展示。

Card(
  elevation: 8,
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(20),
  ),
  child: Container(
    padding: const EdgeInsets.all(24),
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(20),
      gradient: LinearGradient(
        colors: [Colors.blue.shade100, Colors.blue.shade50],
      ),
    ),
    child: Column(
      children: [
        Text(_getResultLabel()),
        const SizedBox(height: 8),
        Text(_result.toStringAsFixed(2)),
      ],
    ),
  ),
)

卡片把结果从输入区域里独立出来,用户输入后能立刻看到反馈。

9.2 结果格式化

源码使用 toStringAsFixed(2) 固定保留两位小数。

Text(
  _result.toStringAsFixed(2),
  style: const TextStyle(
    fontSize: 48,
    fontWeight: FontWeight.bold,
  ),
)

这种格式适合百分比和金额类场景,能避免结果位数忽长忽短造成视觉跳动。

9.3 单位显示

结果右侧单位由当前模式控制。

if (_selectedMode != 1) ...[
  const SizedBox(width: 4),
  Text(
    _selectedMode == 2 ? '%' : '',
    style: TextStyle(
      fontSize: 24,
      color: Colors.blue.shade700,
    ),
  ),
]

这里有一个细节:第一种模式不显示单位,第三种模式显示 %,第二种模式整段单位区域不展示。它符合当前源码表现,但如果把第二种模式视为百分比结果,后续也可以在产品层面让它显示百分号。

十、结果标题与用户理解

10.1 标题生成方法

结果卡片上方的说明来自 _getResultLabel()

String _getResultLabel() {
  switch (_selectedMode) {
    case 0:
      return '${_percentController.text}% of ${_valueController.text}';
    case 1:
      return '${_valueController.text} is what % of ${_percentController.text}';
    case 2:
      return 'Percentage change';
    default:
      return 'Result';
  }
}

这段代码把输入值直接拼入说明文案,使结果与输入之间的关系更明确。

10.2 空输入时的显示

当输入为空时,标题中也会出现空字符串。例如第一种模式初始状态下可能显示 % of。这不是运行错误,只是初始文案不够完整。

状态 页面表现 影响
两个输入框为空 结果为 0.00 不会崩溃
只输入第一个值 使用另一个默认值 0 计算 结果可预测
输入非数字 解析为 0 避免异常

10.3 说明文案的价值

对工具类页面来说,结果数字必须和公式解释同时出现。否则用户很难判断当前看到的是“比例结果”还是“变化率结果”。

十一、快捷示例 ActionChip

11.1 示例区域

页面底部使用 Wrap 展示三个快捷样例。

Wrap(
  spacing: 8,
  runSpacing: 8,
  children: [
    _buildExampleChip('20% of 100 = 20', 0, '100', '20'),
    _buildExampleChip('15 is 30% of 50', 1, '15', '50'),
    _buildExampleChip('Change: 50 to 60 = 20%', 2, '60', '50'),
  ],
)

WrapRow 更适合小屏,因为内容超出宽度后可以自动换行。

11.2 示例按钮实现

Widget _buildExampleChip(
  String label,
  int mode,
  String value,
  String percent,
) {
  return ActionChip(
    label: Text(label, style: const TextStyle(fontSize: 12)),
    onPressed: () {
      setState(() {
        _selectedMode = mode;
        _valueController.text = value;
        _percentController.text = percent;
      });
      _calculate();
    },
  );
}

点击示例后,源码会同时更新模式和两个输入框,再调用 _calculate() 重新计算结果。

11.3 快捷示例的工程价值

快捷示例不只是体验优化,也有工程价值:

  • 可以作为人工验收用例。
  • 可以帮助读者理解三种公式。
  • 可以降低首次打开应用时的空白感。
  • 可以为 Widget 测试提供明确输入样本。

十二、布局与响应式处理

12.1 SingleChildScrollView

页面主体使用 SingleChildScrollView 包裹。

body: SingleChildScrollView(
  padding: const EdgeInsets.all(24),
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      // 模式、输入框、结果卡片、快捷示例
    ],
  ),
)

这样在小屏或横屏场景下,内容不会因为高度不足直接溢出。

12.2 Column 与 stretch

crossAxisAlignment: CrossAxisAlignment.stretch 让子组件横向铺满父容器。

布局元素 作用
Column 从上到下排列页面模块
SizedBox 控制模块间距
Expanded 让三个模式标签均分宽度
Wrap 让快捷示例自动换行

12.3 OpenHarmony 上的布局关注点

OpenHarmony 设备形态可能覆盖手机、平板、窗口化屏幕等场景。这个页面的布局优势是纵向结构清晰,但仍要关注:

  1. 三个模式标签在窄屏下的文字是否换行自然。
  2. 大号结果数字在极端值下是否挤压卡片。
  3. 输入框弹出键盘后页面是否仍可滚动。
  4. ActionChip 在不同字体缩放下是否保持可点击。

十三、生命周期与资源释放

13.1 dispose 方法

两个 TextEditingController 都需要在页面销毁时释放。


void dispose() {
  _valueController.dispose();
  _percentController.dispose();
  super.dispose();
}

这段代码体现了 Flutter 中控制器类资源管理的基本规范。

13.2 为什么要释放控制器

TextEditingController 会持有文本状态和监听能力。如果页面销毁后控制器还被长期保留,可能造成无意义的内存占用。

对象 是否需要释放 原因
TextEditingController 需要 持有输入状态和监听能力
普通 double 字段 不需要 值类型字段随 State 回收
普通 int 字段 不需要 不持有外部资源

13.3 生命周期顺序

一个典型页面生命周期可以理解为:

创建 Widget
创建 State
执行 build
响应用户输入
多次 setState
页面销毁
执行 dispose

这种顺序对理解 Flutter 交互页面很重要。计算器页面虽然简单,但已经包含完整生命周期。

十四、异常输入与边界场景

14.1 空输入

空输入会被 double.tryParse 解析失败,然后通过 ?? 0 回退为 0。

final emptyValue = double.tryParse('') ?? 0;

这保证用户清空输入框时应用不会抛异常。

14.2 除数为 0

第二、第三种模式都会涉及除法。源码通过条件表达式规避除数为 0。

final result = base > 0 ? (value / base) * 100 : 0;

这个策略简单稳定:当分母不是正数时直接返回 0。

14.3 负数与小数

keyboardType: TextInputType.number 表达了数字输入意图,但不同平台输入法对负号和小数点支持不完全一致。业务公式本身可以处理小数,变化率也可以出现负值。

输入类型 源码处理 结果
空字符串 回退为 0 稳定
普通整数 正常计算 稳定
小数 正常计算 稳定
非数字字符 回退为 0 稳定
分母为 0 返回 0 避免异常

十五、Widget 测试设计

15.1 页面渲染测试

可以先验证应用能正常启动并显示标题。

import 'package:flutter_test/flutter_test.dart';
import '../lib/main.dart';

void main() {
  testWidgets('percentage calculator renders title', (tester) async {
    await tester.pumpWidget(const PercentageCalculatorApp());

    expect(find.text('Percentage Calculator'), findsWidgets);
    expect(find.text('Calculate %'), findsOneWidget);
    expect(find.text('% Change'), findsOneWidget);
  });
}

测试重点是根组件、标题和模式按钮是否可见。

15.2 快捷示例测试

快捷示例非常适合做端到端式 Widget 测试。

testWidgets('example chip fills inputs and calculates result', (tester) async {
  await tester.pumpWidget(const PercentageCalculatorApp());

  await tester.tap(find.text('20% of 100 = 20'));
  await tester.pump();

  expect(find.text('20.00'), findsOneWidget);
});

这个测试覆盖了点击、状态写入、公式计算和结果渲染。

15.3 模式切换测试

testWidgets('change mode shows percentage change label', (tester) async {
  await tester.pumpWidget(const PercentageCalculatorApp());

  await tester.tap(find.text('% Change'));
  await tester.pump();

  expect(find.text('New Value'), findsOneWidget);
  expect(find.text('Old Value'), findsOneWidget);
});

这类测试能验证 UI 文案是否随着 _selectedMode 同步更新。

15.4 测试命令

flutter test

如果测试代码与当前组件类名不一致,测试会在编译阶段暴露问题。对于 Flutter 小应用来说,保持测试入口和真实根组件一致非常关键。

十六、OpenHarmony 适配观察

16.1 适配优势

percentage_calculator 的主体逻辑全部位于 Dart 层,没有摄像头、定位、蓝牙、数据库等原生插件依赖,因此跨端适配重点相对集中。

维度 当前项目情况 适配影响
原生插件 无复杂插件 平台侧风险较低
输入控件 标准 TextField 关注键盘与数字格式
图标 Material Icons 关注字体资源和渲染
布局 标准 Flutter Widget 关注屏幕宽度和字体缩放
计算逻辑 Dart 公式 多端结果一致性较高

16.2 OpenHarmony 构建关注点

OpenHarmony 适配时,除了 Flutter 代码本身,还要关注平台工程和构建链路。

flutter clean
flutter pub get
flutter build hap

具体命令取决于使用的 OpenHarmony Flutter 适配环境。对这个项目来说,更重要的是验证运行效果:

  1. 首页是否能正常启动。
  2. 输入框是否能调起数字键盘。
  3. 三种模式点击是否流畅。
  4. 结果卡片是否在不同屏幕宽度下完整显示。
  5. 快捷示例是否能正确写入输入框。

16.3 多端一致性

跨端一致性不是只看能否编译,还要看交互语义是否一致。

场景 Android OpenHarmony 关注点
输入数字 支持 需验证 键盘布局和小数点
点击模式 支持 需验证 触控反馈
滚动页面 支持 需验证 键盘遮挡后的滚动
展示图标 支持 需验证 Material 图标字体
结果计算 支持 支持 Dart 层逻辑一致

十七、可维护性与后续演进

17.1 当前结构的优点

percentage_calculator 当前代码集中,适合教学和快速理解。

  • 入口清晰:main()MaterialApp 再到首页。
  • 状态集中:输入、模式和结果都在同一个 State 中。
  • 公式集中:三种模式统一由 _calculate() 处理。
  • UI 简洁:页面模块按从上到下的顺序排列。

17.2 可以演进的方向

随着功能增加,可以把计算逻辑从 Widget 中抽离。

double calculatePercentOf(double value, double percent) {
  return value * percent / 100;
}

double calculateRatio(double value, double base) {
  return base > 0 ? (value / base) * 100 : 0;
}

double calculateChange(double current, double previous) {
  return previous > 0 ? ((current - previous) / previous) * 100 : 0;
}

抽离后,公式可以直接做单元测试,Widget 测试只关注页面交互。

17.3 模式枚举

如果希望提高可读性,可以用枚举替代裸 int

enum PercentageMode {
  percentOf,
  ratio,
  change,
}

枚举能避免 012 这种魔法数字在项目变大后造成理解成本。

十八、常见问题与优化建议

18.1 为什么第二种模式结果没有显示百分号

源码中 _selectedMode != 1 时才展示右侧单位区域,因此第二种模式不显示 %。这属于当前实现的真实表现。由于第二种模式计算结果本质上也是百分比,产品层可以根据需要统一补充单位展示。

18.2 为什么输入非法字符不会崩溃

因为源码使用了 double.tryParse,解析失败后通过 ?? 0 回退。这个处理对实时输入尤其重要,用户在输入过程中经常会出现临时空值。

18.3 为什么使用 SingleChildScrollView

计算器页面看起来不长,但键盘弹出、字体放大、横屏或小屏设备都可能让可用高度变小。滚动容器能让页面在更多设备上保持可操作。

18.4 为什么快捷示例点击后还要调用计算方法

示例按钮会直接修改控制器文本。为了让结果立即同步,需要在写入文本后调用 _calculate()。这样用户点击后可以直接看到对应结果。

18.5 为什么没有引入状态管理框架

当前页面只有四个核心状态字段,使用 StatefulWidget 已经足够。过早引入状态管理框架会增加样板代码,不一定提升可维护性。

18.6 小数精度如何处理

页面展示层使用 toStringAsFixed(2),它解决的是显示格式问题。对于财务级精度场景,还需要额外考虑十进制精度库、舍入规则和本地化格式。

总结

percentage_calculator 用很少的代码完成了一个 Flutter 小工具应用的完整闭环:从 MaterialApp 启动,到 StatefulWidget 管理输入和模式,再到 _calculate() 执行三种百分比公式,最后通过结果卡片和快捷示例完成即时反馈。

这个项目的价值在于结构清晰、边界明确,非常适合用来理解 Flutter 交互页面的基本写法。对于 OpenHarmony 跨端适配来说,它的业务逻辑主要位于 Dart 层,没有复杂原生插件依赖,因此验证重点集中在输入法、字体、布局、主题和构建链路上。

如果要继续提升它,可以从三个方向入手:抽离公式函数、补充 Widget 测试、优化第二种模式的输入标签和单位显示。这样既能保持小工具的轻量感,也能让代码更适合长期维护。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐