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 技术目标

本文围绕真实源码拆解以下内容:

  1. Flutter 应用入口和深橙色 Material 3 主题。
  2. _controller 如何管理数字输入。
  3. _isPrime_number_factors_properties 如何协作。
  4. _isPrimeNumber 如何使用平方根优化试除。
  5. _getFactors 如何成对枚举因数。
  6. _getProperties 如何组织数字属性。
  7. _isFibonacci 如何循环判断 Fibonacci 数。
  8. _isTriangular 如何循环判断三角数。
  9. _getNextPrimes 如何查找后续 5 个质数。
  10. 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,且 ab 都大于 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

  1. 保存当前数字。
  2. 判断是否为质数。
  3. 获取全部因数。
  4. 获取数字属性。
  5. 触发 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 结构

页面使用 ScaffoldSingleChildScrollView

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 上应重点验证:

  1. 默认显示 17。
  2. 输入 2 显示 IS PRIME。
  3. 输入 1 显示 NOT PRIME。
  4. 输入 18 显示 NOT PRIME。
  5. 输入 0 不显示结果区。
  6. 清空输入框时旧结果仍保留,这是当前源码行为。

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 演进为更实用的跨平台数学工具。

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


相关资源:

Logo

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

更多推荐