Flutter三方库适配OpenHarmony【prime_checker】质数检测器项目完整实战
Flutter三方库适配OpenHarmony【prime_checker】质数检测器项目完整实战
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
prime_checker 是一个基于 Flutter 的质数检测器项目,核心代码位于 lib/main.dart。应用默认检测数字 17,用户输入数字后,页面会判断它是否为质数,并展示全部因数、Even、Odd、Perfect Square、Fibonacci、Triangular 五类数字属性,还会推荐当前数字之后的 5 个质数。点击推荐数字后,输入框会自动更新并重新检测。
这个项目适合学习 Flutter 数学工具在 OpenHarmony 上的适配过程。它覆盖了 数字输入解析、sqrt 优化质数判断、因数成对枚举、Map 属性结果展示、Fibonacci 判断、三角数判断、ActionChip 推荐交互、条件样式结果卡片 和 Material 3 工具型布局。

图片说明:本文围绕 Flutter 数字输入、数学算法和 OpenHarmony 承载工程展开,所有关键代码均来自 prime_checker 的真实源码。
数学工具类应用的价值不只是给出“是或不是”,还要把判断依据、因数结构和相关数字属性展示出来。
一、项目背景与目标
1.1 项目定位
prime_checker 是一个轻量数学检测工具。它不仅判断质数,还会展示因数、数字属性和后续质数。页面输入框默认是 17,因此应用打开后立刻展示一个质数示例。
当前项目真实支持的功能包括:
- 默认输入数字
17。 - 启动时自动检测 17。
- 输入变化时解析整数。
- 判断输入数字是否为质数。
- 小于 2 的数字不是质数。
- 2 是质数。
- 偶数且不等于 2 时不是质数。
- 奇数试除只检查到平方根。
- 展示当前数字全部因数。
- 展示 Even 属性。
- 展示 Odd 属性。
- 展示 Perfect Square 属性。
- 展示 Fibonacci 属性。
- 展示 Triangular 属性。
- 推荐当前数字之后的 5 个质数。
- 点击推荐质数后自动填入输入框并检测。
- 小于 1 的输入会清空结果数据。
1.2 技术目标
本文围绕真实源码拆解以下内容:
- Flutter 应用入口和深橙色 Material 3 主题。
_controller如何管理数字输入。_isPrime、_number、_factors、_properties如何协作。_isPrimeNumber如何使用平方根优化试除。_getFactors如何成对枚举因数。_getProperties如何组织数字属性。_isFibonacci如何循环判断 Fibonacci 数。_isTriangular如何循环判断三角数。_getNextPrimes如何查找后续 5 个质数。- OpenHarmony 侧如何验证输入、结果、属性、推荐质数和滚动布局。
1.3 核心实现速览
| 能力 | 当前实现 | 适配关注点 |
|---|---|---|
| 应用入口 | runApp(const PrimeCheckerApp()) |
确认首屏加载 |
| 主题 | ColorScheme.fromSeed(seedColor: Colors.deepOrange) |
确认深橙色样式 |
| 默认值 | TextEditingController(text: '17') |
确认默认检测 |
| 质数判断 | _isPrimeNumber |
确认平方根优化 |
| 因数列表 | _getFactors |
确认因数排序 |
| 数字属性 | _getProperties |
确认 Map 展示 |
| Fibonacci | _isFibonacci |
确认循环判断 |
| Triangular | _isTriangular |
确认累加判断 |
| 后续质数 | _getNextPrimes |
确认推荐 5 个 |
| 推荐交互 | ActionChip |
确认点击填入 |
二、环境准备与工程结构
2.1 工程结构
项目保持 Flutter 标准结构,同时包含 OpenHarmony 平台工程。
| 文件或目录 | 作用 |
|---|---|
lib/main.dart |
应用入口、数学算法、状态和 UI |
pubspec.yaml |
SDK 约束、Flutter 依赖和 Material 图标配置 |
analysis_options.yaml |
Flutter lint 规则 |
test/ |
Flutter 测试目录 |
ohos/ |
OpenHarmony 平台承载工程 |
README.md |
项目说明文件 |
当前项目没有引入数学三方库,算法都在 Dart 代码中直接实现。
2.2 依赖配置
项目使用 Dart SDK ^3.9.2,依赖 Flutter SDK,并额外使用 Dart 标准库 dart:math。
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
源码中引入数学库:
import 'dart:math' as math;
math.sqrt 用于质数判断、因数枚举和完全平方数判断。
2.3 常用命令
flutter pub get
flutter analyze
flutter test
flutter run
| 命令 | 用途 |
|---|---|
flutter pub get |
获取依赖 |
flutter analyze |
执行静态分析 |
flutter test |
执行测试 |
flutter run |
在目标设备运行 |
OpenHarmony 调试时,还需要结合本地 Flutter OpenHarmony 工具链完成构建、安装和运行。
三、应用入口与主题配置
3.1 import 依赖
项目引入 Flutter Material 和 Dart math。
import 'package:flutter/material.dart';
import 'dart:math' as math;
material.dart 提供 UI 组件,dart:math 提供平方根函数。
3.2 main 函数
入口函数启动根组件。
void main() {
runApp(const PrimeCheckerApp());
}
3.3 PrimeCheckerApp
根组件创建 MaterialApp。
class PrimeCheckerApp extends StatelessWidget {
const PrimeCheckerApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Prime Checker',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
useMaterial3: true,
),
home: const PrimeCheckerHomePage(title: 'Prime Checker'),
);
}
}
这段代码包含三个关键点:
- 应用标题为
Prime Checker。 - 使用
Colors.deepOrange作为主题种子色。 - 首页为
PrimeCheckerHomePage。
四、页面状态设计
4.1 StatefulWidget
输入数字和检测结果会变化,因此页面使用 StatefulWidget。
class PrimeCheckerHomePage extends StatefulWidget {
const PrimeCheckerHomePage({super.key, required this.title});
final String title;
State<PrimeCheckerHomePage> createState() => _PrimeCheckerHomePageState();
}
4.2 状态字段
状态类定义了输入、判断结果、因数和属性。
final TextEditingController _controller = TextEditingController(text: '17');
bool? _isPrime;
int? _number;
List<int> _factors = [];
Map<String, dynamic> _properties = {};
| 字段 | 类型 | 初始值 | 作用 |
|---|---|---|---|
_controller |
TextEditingController |
17 |
管理输入框 |
_isPrime |
bool? |
null |
是否为质数 |
_number |
int? |
null |
当前检测数字 |
_factors |
List<int> |
空列表 | 当前数字因数 |
_properties |
Map<String, dynamic> |
空 Map | 当前数字属性 |
4.3 初始化检测
页面初始化时检测 17。
void initState() {
super.initState();
_checkPrime(17);
}
因此首屏会直接展示 17 是质数、因数为 1 和 17,并展示数字属性。
4.4 生命周期释放
void dispose() {
_controller.dispose();
super.dispose();
}
TextEditingController 在页面销毁时释放,生命周期处理完整。
五、质数判断算法
5.1 _isPrimeNumber
质数判断方法如下:
bool _isPrimeNumber(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i <= math.sqrt(n).toInt(); i += 2) {
if (n % i == 0) return false;
}
return true;
}
5.2 判断流程
| 条件 | 返回 |
|---|---|
n < 2 |
false |
n == 2 |
true |
n % 2 == 0 |
false |
| 存在奇数因子 | false |
| 没有找到因子 | true |
5.3 为什么只检查到平方根
如果 n = a * b,且 a 和 b 都大于 sqrt(n),那么乘积会大于 n。因此只需要检查到平方根。
i <= math.sqrt(n).toInt()
5.4 为什么步长为 2
偶数已经提前排除。
if (n % 2 == 0) return false;
后续只需要检查奇数因子,因此循环每次 i += 2。
六、因数枚举
6.1 _getFactors
因数枚举方法如下:
List<int> _getFactors(int n) {
final factors = <int>[];
for (int i = 1; i <= math.sqrt(n).toInt(); i++) {
if (n % i == 0) {
factors.add(i);
if (i != n ~/ i) {
factors.add(n ~/ i);
}
}
}
factors.sort();
return factors;
}
6.2 成对收集
如果 i 是因数,那么 n ~/ i 也是因数。
factors.add(i);
if (i != n ~/ i) {
factors.add(n ~/ i);
}
完全平方数时,平方根只添加一次。
6.3 排序
factors.sort();
排序后,UI 中的 Chip 会按从小到大展示。
6.4 示例
| n | factors |
|---|---|
| 17 | 1, 17 |
| 18 | 1, 2, 3, 6, 9, 18 |
| 36 | 1, 2, 3, 4, 6, 9, 12, 18, 36 |
七、数字属性计算
7.1 _getProperties
数字属性以 Map 形式返回。
Map<String, dynamic> _getProperties(int n) {
return {
'Even': n % 2 == 0,
'Odd': n % 2 != 0,
'Perfect Square': math.sqrt(n).toInt() * math.sqrt(n).toInt() == n,
'Fibonacci': _isFibonacci(n),
'Triangular': _isTriangular(n),
};
}
7.2 属性表
| 属性 | 判断 |
|---|---|
| Even | n % 2 == 0 |
| Odd | n % 2 != 0 |
| Perfect Square | sqrt(n).toInt()² == n |
| Fibonacci | _isFibonacci(n) |
| Triangular | _isTriangular(n) |
7.3 完全平方数判断
math.sqrt(n).toInt() * math.sqrt(n).toInt() == n
如果平方根取整后再平方等于原数,就说明是完全平方数。
7.4 属性展示
UI 会遍历 _properties.entries。
..._properties.entries.map((entry) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(entry.key),
Icon(
entry.value ? Icons.check_circle : Icons.cancel,
color: entry.value ? Colors.green : Colors.red,
),
],
);
})
true 显示绿色 check,false 显示红色 cancel。
八、Fibonacci 与三角数判断
8.1 _isFibonacci
bool _isFibonacci(int n) {
int a = 0, b = 1;
while (b < n) {
int temp = b;
b = a + b;
a = temp;
}
return b == n || n == 0;
}
该方法从 0、1 开始迭代 Fibonacci 序列,直到 b >= n。
8.2 Fibonacci 示例
| n | 是否 Fibonacci |
|---|---|
| 0 | true |
| 1 | true |
| 2 | true |
| 3 | true |
| 4 | false |
| 5 | true |
| 8 | true |
8.3 _isTriangular
bool _isTriangular(int n) {
int sum = 0;
int k = 1;
while (sum < n) {
sum += k;
if (sum == n) return true;
k++;
}
return false;
}
三角数是从 1 开始连续自然数求和得到的数。
8.4 三角数示例
| n | 累加过程 | 是否三角数 |
|---|---|---|
| 1 | 1 | true |
| 3 | 1+2 | true |
| 6 | 1+2+3 | true |
| 10 | 1+2+3+4 | true |
| 8 | 1+2+3=6,+4=10 | false |
九、主检测流程
9.1 _checkPrime
void _checkPrime(int n) {
if (n < 1) {
setState(() {
_isPrime = null;
_number = n;
_factors = [];
_properties = {};
});
return;
}
setState(() {
_number = n;
_isPrime = _isPrimeNumber(n);
_factors = _getFactors(n);
_properties = _getProperties(n);
});
}
9.2 小于 1 的处理
当 n < 1 时:
_isPrime设为 null。_number仍记录当前值。_factors清空。_properties清空。
UI 中还要求 _number! > 0 才展示结果,因此小于 1 不会展示结果区。
9.3 正常检测流程
对于 n >= 1:
- 保存当前数字。
- 判断是否为质数。
- 获取全部因数。
- 获取数字属性。
- 触发 UI 重建。
9.4 空输入边界
输入框 onChanged 中只有解析成功才调用 _checkPrime。
final n = int.tryParse(value);
if (n != null) {
_checkPrime(n);
}
因此输入框清空时,int.tryParse 返回 null,旧结果不会被清空。这是当前源码的真实行为。
十、后续质数推荐
10.1 _getNextPrimes
List<int> _getNextPrimes(int from, int count) {
final primes = <int>[];
int n = from + 1;
while (primes.length < count) {
if (_isPrimeNumber(n)) {
primes.add(n);
}
n++;
}
return primes;
}
从当前数字的下一个数开始,持续寻找质数,直到数量达到 count。
10.2 推荐数量
UI 中固定取 5 个。
_getNextPrimes(_number!, 5)
10.3 ActionChip
推荐质数使用 ActionChip。
ActionChip(
label: Text(p.toString()),
onPressed: () {
_controller.text = p.toString();
_checkPrime(p);
},
)
点击后会更新输入框并重新检测。
10.4 示例
如果当前数字是 17,后续 5 个质数是:
| 顺序 | 质数 |
|---|---|
| 1 | 19 |
| 2 | 23 |
| 3 | 29 |
| 4 | 31 |
| 5 | 37 |
十一、输入框与结果卡片
11.1 数字输入框
TextField(
controller: _controller,
keyboardType: TextInputType.number,
decoration: InputDecoration(
labelText: 'Enter a number',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
),
style: const TextStyle(fontSize: 24),
onChanged: (value) {
final n = int.tryParse(value);
if (n != null) {
_checkPrime(n);
}
},
)
移动端会优先显示数字键盘。
11.2 结果卡片
结果区只在 _number != null && _number! > 0 时显示。
if (_number != null && _number! > 0) ...[
Card(...),
]
11.3 质数状态样式
质数时使用深橙色和星形图标。
Icon(
_isPrime == true ? Icons.star : Icons.close,
size: 64,
color: _isPrime == true ? Colors.deepOrange : Colors.grey,
)
非质数时使用灰色和 close 图标。
11.4 结果文案
Text(
_isPrime == true ? 'IS PRIME' : 'NOT PRIME',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: _isPrime == true ? Colors.deepOrange : Colors.grey,
),
)
状态反馈清晰直接。
十二、Factors 与 Properties UI
12.1 Factors 卡片
因数以 Chip 展示。
Wrap(
spacing: 8,
runSpacing: 8,
children: _factors.map((f) {
return Chip(
label: Text(f.toString()),
backgroundColor: _isPrime == true && f != 1 && f != _number
? Colors.orange.shade100
: Colors.grey.shade100,
);
}).toList(),
)
对于质数,因数只有 1 和自身,因此 f != 1 && f != _number 不会命中。也就是说质数状态下橙色中间因数高亮分支基本不会实际出现。
12.2 Properties 卡片
属性用 Row 展示名称和图标。
..._properties.entries.map((entry) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(entry.key),
Icon(
entry.value ? Icons.check_circle : Icons.cancel,
color: entry.value ? Colors.green : Colors.red,
),
],
),
);
})
12.3 属性卡片内容
| 属性 | 展示 |
|---|---|
| true | 绿色 check |
| false | 红色 cancel |
这种展示方式比单纯文本更直观。
十三、页面布局
13.1 Scaffold 结构
页面使用 Scaffold 和 SingleChildScrollView。
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [],
),
),
);
13.2 内容顺序
| 顺序 | 区域 | 作用 |
|---|---|---|
| 1 | 输入框 | 输入数字 |
| 2 | 结果卡片 | 展示是否为质数 |
| 3 | Factors 卡片 | 展示全部因数 |
| 4 | Properties 卡片 | 展示数字属性 |
| 5 | Next Prime Numbers 卡片 | 推荐后续质数 |
13.3 滚动容器意义
数字结果、因数、属性和推荐质数区域较多,小屏设备需要滚动访问。SingleChildScrollView 可以避免内容溢出。
十四、OpenHarmony 适配要点
14.1 基础组件验证
当前项目使用的 Flutter 组件包括:
| 组件 | 作用 | OpenHarmony 关注点 |
|---|---|---|
MaterialApp |
应用根组件 | 首屏加载 |
Scaffold |
页面骨架 | AppBar 与 Body |
TextField |
数字输入 | 软键盘、解析 |
Card |
结果分组 | 圆角、阴影、渐变 |
Wrap |
因数和质数列表 | 自动换行 |
Chip |
因数展示 | 文本和背景 |
ActionChip |
推荐质数 | 点击响应 |
Icon |
状态和属性 | 颜色与图标 |
SingleChildScrollView |
页面滚动 | 小屏访问 |
14.2 输入验证
OpenHarmony 上应重点验证:
- 默认显示 17。
- 输入 2 显示 IS PRIME。
- 输入 1 显示 NOT PRIME。
- 输入 18 显示 NOT PRIME。
- 输入 0 不显示结果区。
- 清空输入框时旧结果仍保留,这是当前源码行为。
14.3 数学结果验证
可用以下数字验证:
| 输入 | 预期 |
|---|---|
| 17 | 质数,因数 1 和 17 |
| 18 | 非质数,因数 1、2、3、6、9、18 |
| 36 | 非质数,Perfect Square 为 true |
| 21 | Triangular 为 true |
| 13 | Fibonacci 为 true |
14.4 推荐质数验证
点击 Next Prime Numbers 中任意 ActionChip 后:
- 输入框文本更新为该质数。
- 结果卡片显示 IS PRIME。
- 因数列表更新。
- 属性列表更新。
- 新的后续质数列表更新。
数学工具的适配重点是输入、算法结果和 UI 状态同步。只看页面能打开是不够的。
十五、测试与验证
15.1 初始页面测试
Widget 测试可以验证默认检测 17。
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('prime checker shows default result', (tester) async {
await tester.pumpWidget(const PrimeCheckerApp());
expect(find.text('Prime Checker'), findsWidgets);
expect(find.text('17'), findsWidgets);
expect(find.text('IS PRIME'), findsOneWidget);
expect(find.text('Factors'), findsOneWidget);
});
}
15.2 非质数测试
testWidgets('detects non-prime number', (tester) async {
await tester.pumpWidget(const PrimeCheckerApp());
await tester.enterText(find.byType(TextField), '18');
await tester.pump();
expect(find.text('NOT PRIME'), findsOneWidget);
});
15.3 推荐质数测试
testWidgets('next prime chip updates number', (tester) async {
await tester.pumpWidget(const PrimeCheckerApp());
await tester.tap(find.text('19'));
await tester.pump();
expect(find.text('19'), findsWidgets);
expect(find.text('IS PRIME'), findsOneWidget);
});
15.4 手工验证矩阵
| 场景 | 操作 | 预期 |
|---|---|---|
| 首次打开 | 启动应用 | 默认检测 17 |
| 输入质数 | 输入 29 | 显示 IS PRIME |
| 输入合数 | 输入 30 | 显示 NOT PRIME |
| 查看因数 | 输入 36 | 展示多个因数 |
| 查看属性 | 输入 13 | Fibonacci 为 true |
| 后续质数 | 点击推荐质数 | 输入框和结果更新 |
| 输入 0 | 输入 0 | 结果区隐藏 |
| 清空输入 | 删除输入内容 | 旧结果不会自动清空 |
十六、常见问题与优化建议
16.1 为什么质数判断只到平方根
如果一个数有因数,至少有一个因数不大于平方根。因此只检查到 sqrt(n) 即可。
for (int i = 3; i <= math.sqrt(n).toInt(); i += 2)
这比从 2 检查到 n-1 更高效。
16.2 为什么跳过偶数
2 之外的偶数都不是质数。
if (n % 2 == 0) return false;
提前排除偶数后,后续循环只需要检查奇数。
16.3 为什么空输入不会清空结果
输入变化时只在解析成功时调用 _checkPrime。
final n = int.tryParse(value);
if (n != null) {
_checkPrime(n);
}
空字符串解析失败,因此不会更新状态。可以在 n == null 时清空 _number、_factors 和 _properties。
16.4 如何优化完全平方数判断
当前代码重复调用 math.sqrt(n).toInt()。
math.sqrt(n).toInt() * math.sqrt(n).toInt() == n
可以先保存平方根整数:
final root = math.sqrt(n).toInt();
final isSquare = root * root == n;
这样更清晰,也减少重复计算。
16.5 如何处理负数
当前 _checkPrime 对小于 1 的数清空因数和属性,并隐藏结果区。
if (n < 1) {
_isPrime = null;
_factors = [];
_properties = {};
}
如果需要显示“负数不是质数”的提示,可以调整 UI 条件。
16.6 如何提升大数性能
当前算法适合中小整数。更大数字可以考虑:
- 缓存平方根。
- 使用更高效的试除步进。
- 增加输入上限。
- 使用异步计算避免 UI 卡顿。
- 引入更专业的素性测试算法。
十七、工程扩展方向
17.1 抽取检测结果模型
可以把检测结果封装成对象。
class PrimeAnalysis {
final int number;
final bool isPrime;
final List<int> factors;
final Map<String, bool> properties;
const PrimeAnalysis({
required this.number,
required this.isPrime,
required this.factors,
required this.properties,
});
}
页面只负责展示分析结果。
17.2 抽取数学工具函数
数学逻辑可以放到独立工具类。
class NumberMath {
static bool isPrime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (var i = 3; i <= math.sqrt(n).toInt(); i += 2) {
if (n % i == 0) return false;
}
return true;
}
}
抽取后更容易写单元测试。
17.3 增加历史记录
可以保存最近检测过的数字。
final List<PrimeAnalysis> history = [];
void addHistory(PrimeAnalysis analysis) {
history.insert(0, analysis);
if (history.length > 10) {
history.removeLast();
}
}
历史记录适合学习类工具。
17.4 增加范围质数表
可以生成某个范围内的质数。
List<int> primesInRange(int start, int end) {
final result = <int>[];
for (var n = start; n <= end; n++) {
if (NumberMath.isPrime(n)) result.add(n);
}
return result;
}
范围质数表可以和筛法结合,进一步讲解算法优化。
总结
prime_checker 是一个完整的 Flutter 数学检测工具。它用 TextEditingController 管理数字输入,用 _isPrimeNumber 通过平方根优化判断质数,用 _getFactors 成对收集并排序因数,用 _getProperties 聚合 Even、Odd、Perfect Square、Fibonacci、Triangular 五类属性,再用结果卡片、Factors Chip、Properties 列表和 Next Prime Numbers ActionChip 形成完整交互闭环。
从 OpenHarmony 适配角度看,这个项目适合验证 Flutter 数字输入、实时解析、滚动布局、渐变卡片、Chip 换行、ActionChip 点击、图标状态和数学结果展示。排查路径也很明确:质数判断不对看 _isPrimeNumber,因数不对看 _getFactors,属性不对看 _getProperties,推荐质数不对看 _getNextPrimes,空输入旧结果保留则来自当前 onChanged 的解析分支。
掌握这个项目后,可以继续扩展检测历史、范围质数表、埃氏筛法、异步计算、大数限制和更完整的数字属性分析,让质数检测器从单数字 Demo 演进为更实用的跨平台数学工具。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
更多推荐



所有评论(0)