【开源鸿蒙跨平台开发先锋训练营】Flutter实现图表显示
1, flutter 应用中的图表生成可以使用fl_chart插件,方便易上手。2. AI工具使用很方便, 但是也有找错方向的时候,就会浪费时间资源。所以还是需要加强自身知识的学习,这样可以检查确认AI的修改,防止错误或者加强与AI的合作更高效的完成工作。3. 等插件适配文件解决了之后再来更新鸿蒙截屏图片。
欢迎加入开源鸿蒙跨平台社区:
https://openharmonycrossplatform.csdn.net
背景
关于统计数据的展示,图表时避免不了的。所以这里联系学习一下Flutter中关于图表展示功能的实现。
方案咨询
可选方案概览
fl_chart: 纯 Dart、支持折线/柱状/饼图/雷达等,动画与样式丰富,集成简单,社区活跃。
syncfusion_flutter_charts: 功能最全(海量图表类型、交互、导出、图例、数据缩放等),企业级、性能优,商业许可/社区许可策略需注意。
charts_flutter (Google charts): 曾由 Google 提供,API 结构化但维护缓慢、样式定制有限。
charts_painter / chart_sparkline / mp_chart_flutter: 轻量或专注单一图表(如微图、Sparkline、基于 MPAndroidChart 的移植),适合特定场景。
自绘 (CustomPainter): 最高自由度与最小依赖,适合极定制或超轻量场景,但工作量大、需处理交互/动画/性能。
WebView + JS 图表 (Chart.js / ECharts / D3 等): 可复用成熟 JS 可视化库,适合复杂可视化或已有 JS 逻辑,但引入 WebView、桥接和样式隔离成本。
选择fl_chart方案的理由:
选择 fl_chart 的原因
易用且快速集成:API 直观,上手快——适合在现有页面快速展示饼图(少样式/交互改造)。
纯 Dart/跨平台:无需平台通道或 WebView,兼容移动和桌面。
视觉与动画支持好:默认样式美观,饼图标签、动画、区域样式容易配置。
体积与依赖友好:相比企业级库更轻量,无复杂许可限制(适合开源/个人项目)。
与当前代码契合:我们的统计只是按 branch 计数并展示占比,fl_chart 足够且实现速度最快。
什么时候改用其它库
需要高级交互、导出、海量数据或企业支持 → 考虑
syncfusion_flutter_charts。追求极致定制且愿意投入实现成本 → 采用
CustomPainter。想复用复杂现有 JS 图形或做非常复杂的可视化 → 使用 WebView + ECharts/Chart.js。
修改要点:
pubspec.yaml
追加对fl_chart的依赖
diff --git a/pubspec.yaml b/pubspec.yaml
index e619a21..91427ee 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -40,6 +40,7 @@ dependencies:
shared_preferences: ^2.1.1
flutter_secure_storage: ^8.0.0
provider: ^6.0.5
+ fl_chart: ^1.1.1^M
# shared_preferences:
# git:
# url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
这里一开始遇到了fl_chart版本兼容性问题,如下所示错误
PS D:\flutter\projects\pipeline> flutter run -d chrome
Flutter assets will be downloaded from https://storage.flutter-io.cn. Make sure you trust this source!
Launching lib\main.dart on Chrome in debug mode...
../../pub/hosted/pub.dev/fl_chart-0.60.0/lib/src/utils/utils.dart:265:20: Error: Member not found:
'MediaQuery.boldTextOverride'.
if (MediaQuery.boldTextOverride(context)) {
^^^^^^^^^^^^^^^^
Waiting for connection from debug service on Chrome... 11.3s
Failed to compile application.
PS D:\flutter\projects\pipeline>
当让AI去解决的时候,它就开始误入歧途了,各种尝试,各种失败,甚至要放弃fl_chart的方案:(
其实很简单,就是修改为使用最新的版本1.1.1版本就可以了。具体的版本信息和支持情况可以去pub.dev的fl_chart链接去查看(https://pub.dev/packages/fl_chart,可以看到最近的更新为4个月前,所以一般功能的支持应该是没有问题的)
原因简述:
错误来自
fl_chart调用不存在的 APIMediaQuery.boldTextOverride(context),说明你当前的 Flutter SDK 与该fl_chart版本的期望 API 不匹配(包使用了你 SDK 中没有的成员)。可选修复(按推荐顺序)
升级 Flutter 到与
fl_chart 0.60.0兼容的版本(长期稳妥)。
优点:不改包代码,保持包最新;缺点:可能需要切换 channel / 自定义构建。
使用
fl_chart的旧版本(在你的 SDK 上已知可用)。
修改 pubspec.yaml 把
fl_chart: ^0.60.0换成较老的版本(例如 0.58.x / 0.55.x——在你本地尝试哪个能通过构建)。然后运行
flutter pub get。临时修补
fl_chart源码(快速验证 / 临时解决)——把出现MediaQuery.boldTextOverride(context)的地方改为兼容写法:
将
MediaQuery.boldTextOverride(context)
替换为
MediaQuery.maybeOf(context)?.boldText ?? false位置示例(从你的错误):
.../pub/hosted/pub.dev/fl_chart-0.60.0/lib/src/utils/utils.dart修改后执行
flutter clean然后flutter pub get/flutter run。注意:这是对缓存中包的本地修改,
pub upgrade可能会覆盖它;更稳妥的做法是 fork 包并在 pubspec.yaml 使用 git 依赖。
lib/main.dart
fl_chart package的导入
+import 'package:fl_chart/fl_chart.dart';
将StatisticsPage 类改为继承自StatefulWidget
// 统计页面
-class StatisticsPage extends StatelessWidget {
+class StatisticsPage extends StatefulWidget {^M
const StatisticsPage({super.key});
+ @override^M
+ State<StatisticsPage> createState() => _StatisticsPageState();^M
+}^M
_StatisticsPageState类的追加
- 首先是页面显示时对数据的加载,其实这一步应该可以和第一个tab页面共享所加载的数据,不需要再单独去网络请求一次了。
- 饼图已经对应标签的生成, 主要使用到了PieChartSectionData, PieChart, PieChartData, PieTouchData等类
具体实现请查看下面代码diff
+class _StatisticsPageState extends State<StatisticsPage> {^M
+ bool _loading = true;^M
+ String? _error;^M
+ Map<String, int> _counts = {};^M
+ int _total = 0;^M
+^M
+ @override^M
+ void initState() {^M
+ super.initState();^M
+ WidgetsBinding.instance.addPostFrameCallback((_) => _loadStats());^M
+ }^M
+^M
+ Future<void> _loadStats() async {^M
+ setState(() {^M
+ _loading = true;^M
+ _error = null;^M
+ });^M
+^M
+ try {^M
+ final settings = Provider.of<Settings>(context, listen: false);^M
+ final items = await getPipelinesPage(settings.projectID, settings.domain, settings.token, 1, settings.perPage);^M
+ final Map<String, int> map = {};^M
+ for (var p in items) {^M
+ map[p.ref] = (map[p.ref] ?? 0) + 1;^M
+ }^M
+ setState(() {^M
+ _counts = map;^M
+ _total = items.length;^M
+ });^M
+ } catch (e) {^M
+ setState(() {^M
+ _error = e.toString();^M
+ });^M
+ } finally {^M
+ setState(() {^M
+ _loading = false;^M
+ });^M
+ }^M
+ }^M
+^M
+ List<PieChartSectionData> _sections() {^M
+ final colors = Colors.primaries;^M
+ final sections = <PieChartSectionData>[];^M
+ if (_total == 0) return sections;^M
+ int i = 0;^M
+ _counts.forEach((branch, count) {^M
+ final value = count.toDouble();^M
+ final percent = (count / _total) * 100;^M
+ sections.add(PieChartSectionData(^M
+ value: value,^M
+ color: colors[i % colors.length].withOpacity(0.9),^M
+ title: '${percent.toStringAsFixed(0)}%',^M
+ radius: 60,^M
+ titleStyle: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold, color: Colors.white),^M
+ ));^M
+ i += 1;^M
+ });^M
+ return sections;^M
+ }^M
+^M
@override
Widget build(BuildContext context) {
- return const Center(
+ if (_loading) return const Center(child: CircularProgressIndicator());^M
+ if (_error != null) return Center(child: Text('加载统计失败:$_error'));^M
+ if (_total == 0) return const Center(child: Text('暂无数据'));^M
+^M
+ final entries = _counts.entries.toList();^M
+ final sections = _sections();^M
+^M
+ return Padding(^M
+ padding: const EdgeInsets.all(16.0),^M
child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
children: [
- Icon(Icons.bar_chart, size: 64, color: Colors.grey),
- SizedBox(height: 16),
- Text('统计页面', style: TextStyle(fontSize: 18)),
- SizedBox(height: 8),
- Text('功能开发中...', style: TextStyle(color: Colors.grey)),
+ const Text('分支占比', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),^M
+ const SizedBox(height: 12),^M
+ Expanded(^M
+ child: Row(^M
+ children: [^M
+ Expanded(^M
+ child: AspectRatio(^M
+ aspectRatio: 1,^M
+ child: PieChart(^M
+ PieChartData(^M
+ sections: sections,^M
+ centerSpaceRadius: 36,^M
+ sectionsSpace: 2,^M
+ pieTouchData: PieTouchData(enabled: true),^M
+ ),^M
+ ),^M
+ ),^M
+ ),^M
+ const SizedBox(width: 12),^M
+ Expanded(^M
+ child: ListView.builder(^M
+ itemCount: entries.length,^M
+ itemBuilder: (context, index) {^M
+ final e = entries[index];^M
+ final percent = (e.value / _total) * 100;^M
+ final color = Colors.primaries[index % Colors.primaries.length];^M
+ return ListTile(^M
+ leading: CircleAvatar(backgroundColor: color, radius: 8),^M
+ title: Text(e.key),^M
+ trailing: Text('${e.value} (${percent.toStringAsFixed(0)}%)'),^M
+ );^M
+ },^M
+ ),^M
+ ),^M
+ ],^M
+ ),^M
+ ),^M
+ ElevatedButton(onPressed: _loadStats, child: const Text('刷新')),^M
],
),
);
但是同样因为在我的页面中使用了需要鸿蒙适配的包依赖,还没有解决,鸿蒙上还不能正常显示。
下面分别为Android, 鸿蒙模拟器,及Chrom浏览器上的截图,供参考。



总结
1, flutter 应用中的图表生成可以使用fl_chart插件,方便易上手。
2. AI工具使用很方便, 但是也有找错方向的时候,就会浪费时间资源。所以还是需要加强自身知识的学习,这样可以检查确认AI的修改,防止错误或者加强与AI的合作更高效的完成工作。
3. 等插件适配文件解决了之后再来更新鸿蒙截屏图片。
更多推荐




所有评论(0)