拿 fl_chart 做柱状图,说简单是真简单,数据一塞配置一写图就出来了;但说麻烦也麻烦,坐标轴标题、数值标签、点击交互、Tooltip,一堆细节不踩几个坑根本调不好。这篇就把我用 BarChart 这套东西的完整经验记录下来,从第一根柱子到能交差的成品图全流程拆开讲,顺手把网上很少说清的坑也一并填了。
我最早接触 fl_chart,是因为某后台项目需要在移动端展示一组门店营业数据,折线图、柱状图、饼图都要上。当时对比过好几个 Flutter 图表库,最后定在 fl_chart 上,一个很现实的原因:它的 API 风格和 Flutter 本身高度统一,Clearable 之类的概念几乎不用额外学,而且数据刷新走声明式重建,和 Flutter 的 setState 思维完全同频。尤其 BarChart,在处理分类对比、趋势分布这类场景时,直观程度远超表格。
这篇适合两类人看:一类是刚接触 fl_chart 的 Flutter 开发,照着抄能直接跑通第一个图;另一类是已在用但总被细节折磨的朋友,比如为什么数值标签不显示、为什么柱子和 x 轴标签对不齐、为什么点击事件老是没反应。我会把 BarChart 的核心配置逐层拆解,从环境准备一直写到性能调优和排坑,全程附代码和解释,最后再给一套我整理好的模板,直接改数据就能用。
1. 先搞懂 BarChart 的定位和核心分层
1.1 为什么是 fl_chart,以及 BarChart 在图表库里的生态位
Flutter 社区的图表库其实不少,但生态成熟度、维护活跃度、文档完整度综合看下来,fl_chart 是绕不开的那一个。它的 GitHub Star 常年排在前列,对于团队选型来说,社区活跃意味着两个直接好处:一是遇到问题基本能搜到前人踩坑记录;二是新的 Flutter 版本发布,库的兼容性跟进会比较及时。相比之下,某些老牌图表库虽然功能也全,但多年不更新,遇到 Flutter 3.x 的 Breaking Changes 就经常翻车。
BarChart 在 fl_chart 里承担的是“分类数据对比”的展示职能。比如说本周每天订单量、各产品线销售额、不同渠道的用户转化率,这类横向比较的场景,柱状图天生的视觉长度差就能让人一眼抓住大小关系。同时它也可以兼任一些简单的时间序列展示,只要分类数量不过密,x 轴放日期或者周几,阅读体验仍然很舒服。
1.2 BarChart 解决什么问题
日常开发里,BarChart 最典型的三类使用场景:
- 统计面板:后台系统的核心指标卡旁边,通常要配一个柱状图展示近 7 天或 30 天的趋势。
- 排行榜对比:多个门店、多条产品线之间的横向比较,排序后直接把量级差异可视化。
- 简单分布展示:例如年龄段分布、价格区间订单数等。
它解决的核心问题是:把一串冷冰冰的数字转成用户一眼能看懂的长度关系。这个“长度关系”听起来简单,实际要做好非常考验细节——柱子宽度比例、间距、坐标轴刻度密度、标签可读性,任何一个配置不当,都会让图表“差一口气”。
1.3 什么时候不建议用 BarChart
这也是我后来才想明白的。BarChart 本质上适合少分类、差异大的数据。如果你有上百个分类,柱子会密集到看不清,这时候应该优先考虑横向滚动列表搭配迷你图表;如果数据是连续且变化非常细密的走势,折线图更合适;如果是占比关系,饼图或者环形图更好。图表选型不是越复杂越好,而是“哪种视觉编码最匹配数据特征”。fl_chart 这套库的好处在于,它的系列实现得非常统一,你掌握了 BarChart,顺手切到 LineChart、PieChart 的学习成本极低。
2. 环境准备与最简柱子跑通
2.1 版本选择与依赖安装
直接在 pubspec.yaml 里加依赖:
dependencies: fl_chart: ^5.5.0版本这里有个经验:尽量不要用太激进的主版本。fl_chart 的 API 在不同主版本之间差异比较大,比如从 0.x 到 1.x,再到现在的 5.x、6.x,很多构造参数的写法都换过。如果你是在老项目上升级,直接从旧版本跨主版本升级,大概率要动大量代码。新项目就直接用当前最新稳定版;老项目若只是加个图表,老老实实保持原有版本,看该版本对应的文档,别硬升。
安装完成后,在需要使用的页面:
import 'package:fl_chart/fl_chart.dart';2.2 最小可运行代码
SizedBox( height: 280, child: BarChart( BarChartData( barGroups: [ BarChartGroupData(x: 0, barRods: [ BarChartRodData(toY: 4), ]), BarChartGroupData(x: 1, barRods: [ BarChartRodData(toY: 7), ]), BarChartGroupData(x: 2, barRods: [ BarChartRodData(toY: 3), ]), BarChartGroupData(x: 3, barRods: [ BarChartRodData(toY: 9), ]), ], ), ), )把这段代码扔进一个页面,你会看到屏幕上出现四根灰色柱子,高度分别对应 4、7、3、9。这就已经是一个能用的 BarChart 了。但很显然,它离“能见人”还差得远——没有颜色、没有坐标轴、没有标签、没有任何反馈。接下来就逐步做加法。
2.3 数据模型三层结构
BarChart 的数据结构是三层嵌套,刚上手的人很容易搞混。
- BarChartData:整张图的配置,包括背景、边框、网格、坐标轴、工具条、动画时长。
- BarChartGroupData:每一组柱子,对应 x 轴上的一个刻度位置。每个组里可以放多根柱子(这就是分组柱状图的由来)。
- BarChartRodData:单根柱子,包括高度 toY、颜色、宽度、圆角、渐变等。
理解了这个三层结构,后面所有配置都能对号入座:想调整体图,改 BarChartData;想调某一组的间距和位置,改 BarChartGroupData;想调一根柱子的长相和高度,改 BarChartRodData。
3. 让柱子有信息量:坐标轴、格线与范围
3.1 坐标轴标题其实不是最关键的,刻度才是
很多人一上来就想在 x 轴底部显示“周一、周二”之类的中文标签,在 y 轴显示数值。这没问题,但要注意:刻度标签本身可读性优先。先设置 FlTitlesData:
titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (double value, TitleMeta meta) { const week = ['', '周一', '周二', '周三', '周四', '周五', '周六', '周日']; return Text( week[value.toInt()], style: const TextStyle(fontSize: 12, color: Colors.black54), ); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, getTitlesWidget: (double value, TitleMeta meta) { return Text( value.toInt().toString(), style: const TextStyle(fontSize: 12, color: Colors.black54), ); }, ), ), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false), ), ),这段代码里有个容易踩坑的点:getTitlesWidget 里的 value 是 double,而且数组下标下标从 0 开始。如果你直接传 x 值等于 1、2、3 去映射中文标签,第一个位置会偏移。我的习惯是让 BarChartGroupData 的 x 从 0 开始编号,这样和数组下标正好对上。
reservedSize 也很关键,它决定了图表区域为左侧 y 轴标签预留多少横向宽度。设小了,多位数的 y 轴标签会被截断;设大了,图表区域被挤压。经验值:两位数标签 28~32,三位数 36~40。
3.2 格线和背景:图表的“底妆”
网格线的设置藏在 gridData 里:
gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: 1, getDrawingHorizontalLine: (value) { return FlLine( color: Colors.black.withOpacity(0.08), strokeWidth: 1, ); }, ),注意 horizontalInterval 这个参数,它决定 y 轴网格线的间隔。默认是 1,如果你的数值范围是 0 到 100,那网格线会很稀疏;如果数据量级在 0 到 5 之间,每 1 一条线又过于密集。这里按实际数据范围调整,比如数据在 0~100 时,设成 20 比较舒服。
还有一个很多人不知道的坑:当数据中有小数时,horizontalInterval 设成整数 1 可能导致最后的格线超过图表上边界,系统自动多画一条线,视觉上有点奇怪。解决办法是把 maxY 稍微调高到数据上限的 1.1 倍左右,给顶部留点余量,顺便也给数值标签留出呼吸空间。
3.3 minY、maxY 与区间自动伸缩
默认情况下,fl_chart 会根据数据自动计算 y 轴范围。但在实际业务里,这个“自动”经常不满足需求。比如你的数据最大值是 85,但你想强调所有柱子都没到 100 这个 KPI 线,那就可以:
minY: 0, maxY: 100,再比如某些负值场景,柱状图从零点往下生长,MinY 就要设成负数:
BarChartRodData(toY: -3, fromY: 0),这里提一个进阶的点:BarChartRodData 的 fromY 在 fl_chart 旧版本里是直接存在的,但新版中它被移除了,转而用rodStackItems来实现从某个基准点开始画柱子的效果。我后面有一节专门讲版本迁移,这里先提个醒:如果你在网上搜到老代码里有 fromY,但在你本地编译报错,别慌,十有八九是版本差异。
4. 柱子外观的精细控制:这一步做完就好看一半
4.1 颜色、圆角与宽度
BarChartRodData( toY: 7.5, width: 18, color: Colors.blueAccent, borderRadius: BorderRadius.vertical(top: Radius.circular(6)), )width 控制柱子的宽度,单位是逻辑像素。这里有个视觉平衡问题:宽度太大,柱子和柱子之间会显得拥堵;宽度太小,又显得单薄。实际项目中,我都是先确定图表总宽和分组数,再反推合适的柱宽。一般来说,柱子间距保持在柱宽的 1.5~2 倍比较舒服,当然你也可以通过 BarChartData 里的 alignmentSpaceRatio 和 barWidth 两个参数来调整。
补充一句:如果你想让柱子有立体感或者渐变效果,可以直接给整根 BarChartRodData 上渐变:
BarChartRodData( toY: 7.5, width: 18, gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, colors: [Colors.blue.shade800, Colors.blue.shade200], ), )渐变这东西,用得好是锦上添花,用不好就是花里胡哨。我的建议是:数据展示类界面尽量克制,一个页面里最多用一到两组渐变柱子用来强调重点数据。
4.2 多柱分组:一组的多个柱子怎么排
当你需要对比同一分类下的两个指标时,就可以用分组柱子:
BarChartGroupData( x: 0, barRods: [ BarChartRodData(toY: 4, width: 8, color: Colors.blue), BarChartRodData(toY: 6, width: 8, color: Colors.orange), ], )这时候你会发现,同一组的几根柱子会自动排布在同一 x 位置。如果想调整组里柱子的相对间距和整体宽度,就需要改到 BarChartData 这一层,看 barAlignment 和 alignmentSpaceRatio。这里不展开太多,实际中我一般让两根并排柱子总宽度和单根大柱子相近,视觉才一致。
4.3 图表整体内外边距
BarChartData( alignmentSpaceRatio: 0.1, barWidth: 18, // ... )alignmentSpaceRatio 本质上控制了柱子之间的空隙占总量的比例。数值越小,柱子间距越小;数值越大,柱子越“松散”。如果图标和左侧 y 轴标签距离过近,还可以在 FlTitlesData 左右两侧放 invisible 占位轴,用 reservedSize 撑开间距,这个技巧在处理复杂界面布局时相当好用。
4.4 柱子的下边距和边界框
很多人会纠缠为什么柱子为什么紧紧贴在底部边框上,这不是 bug。在 BarChartData 里,你可以用barAlignment来调整柱子相对轴线的对齐基准,同时可以通过borderData: FlBorderData(show: false)完全隐藏四周边框,让图表看起来更轻盈。做卡片嵌入时通常不显示边框,做独立报表时才画边框,这点看使用场景。
5. 让数字标签出现在柱子上方
5.1 官方默认的做法
fl_chart 本身不直接在柱子上方显示数值,它是通过 Tooltip 机制来展示数值的。默认配置下,长按或点击柱子会弹出一个工具提示条,里面包含 x 轴和 y 轴的数值信息。这是 API 设计上的一个取舍:它希望把“常规显示”和“临时查看”分开,避免信息过载。
但实际业务里,每个柱子顶部直接写数值的需求非常普遍。fl_chart 也提供了另一种方式:自定义 Tooltip 组件。可以在 BarTouchTooltipData 里设置 getTooltipColor 和 getTooltipItem,把 Tooltip 的背景色改成透明,只保留文本,这样视觉上就会“常驻”在柱子上方。
5.2 一个更土但绝对好使的方案
如果上面的方式你嫌麻烦,或者透明背景在视觉上不够干净,还有一个思路:额外用一个 Stack 在图表上层对齐画 Text。具体做法是给每个分组算好屏幕坐标,然后定位 Text,但这牵扯到图表内部坐标换算,属实有些繁琐。
所以我更推荐第一种方案:改造 Tooltip 成常驻模式。实际效果等同于柱子顶部显示数字,而且省去了坐标换算的麻烦。
5.3 Tooltip 的改造要点
barTouchData: BarTouchData( enabled: true, touchTooltipData: BarTouchTooltipData( getTooltipColor: (_) => Colors.transparent, tooltipPadding: EdgeInsets.zero, tooltipMargin: 0, getTooltipItem: (group, groupIndex, rod, rodIndex) { return BarTooltipItem( '${rod.toY}', const TextStyle(fontSize: 14, fontWeight: FontWeight.bold, color: Colors.black), ); }, ), ),注意:tooltipMargin 如果你希望数值与柱子顶部有一点间距,可以设置一个小正数。如果设置成 0,文字会贴在柱子边缘,具体视设计稿而定。还有一点,getTooltipColor 返回透明后,默认的触摸反馈阴影还是会有的,不影响日常使用,如果你觉得干扰体验,可以直接将 BarTouchData.enabled 保持开启,同时不配置额外的 renderer。
这种“常驻数值”的做法唯一的硬伤是:用户触摸柱子时,Tooltip 会叠加显示,视觉上有轻微变化。不过这个变化正好能起到交互反馈的作用,算是因祸得福。
6. 交互:点击柱子有响应
6.1 触摸事件拿到的到底是什么
fl_chart 的触摸反馈机制非常统一,通过 BarTouchData 的 touchCallback 获得:
barTouchData: BarTouchData( touchCallback: (FlTouchEvent event, BarTouchResponse? response) { if (response == null || response.spot == null) return; final group = response.spot!.touchedBarGroup; if (group == null) return; // group.x 就是被点击分组的 x 值 }, ),这里的一个常见误区是:很多人以为 touchCallback 每次触摸都会触发 setState 来更新图表。这是不必要的,而且可能会导致动画重播,视觉上像是图表在“闪烁”。如果你只是在点击后弹个提示、跳个页面,直接处理业务逻辑就行,不要 setState。
6.2 怎么区分“点中了柱子”和“点在了空白处”
event 参数里有一个 isInterestedForThisSpot 之类的判断。更直接的方式是看 response.spot == null。当你点空白区域时,fl_chart 不会生成事件,spot 为空;只有点在你配置的绘图区域附近的目标上时,spot 才有值。这个注意点对于活跃用户点击体验的优化非常重要——不要让空白点击触发任何东西。
6.3 按住与松开的手势细节
如果你需要做一个“点击后柱子高亮”的效果,常规做法是记录当前选中分组的 x 值,然后用颜色区分。这个场景下建议在 touchCallback 里判断 event is FlTapUpEvent 再处理,避免按下就变色、滑动又变色这种误触。
touchCallback: (event, response) { if (event is FlTapUpEvent) { setState(() { selectedX = response?.spot?.touchedBarGroup?.x; }); } },然后在构建 BarChartRodData 时,根据 selectedX 动态返回不同的颜色。这里要提个醒:每次触摸都 setState,图表动画会重新跑一遍,体验上确实有点跳。想要顺滑高亮,可以在 BarChartData 里把 duration 设成 Duration.zero,或者把 swapAnimationDuration 调短。fl_chart 本身支持动画时长控制,把动画时间压到 100ms 左右的话,视觉过渡就很自然。
7. 数据刷新与动画控制
7.1 怎么更新图表数据
fl_chart 的数据是声明式的,要想刷新,最简单的方式就是 setState 里换掉 barGroups。但这里有个坑:如果你在 setState 前后给 BarChart 传了一个全新的 BarChartData 对象,默认情况下它会执行一次动画过渡(swap animation)。这个动画有时候你不想让它发生,可以用swapAnimationDuration: Duration.zero来禁用。
BarChartData( swapAnimationDuration: Duration(milliseconds: 0), swapAnimationCurve: Curves.linear, barGroups: newGroups, )7.2 初次载入动画 vs 数据刷新动画
这里有一个很好的使用策略:初次载入时保留动画,数据刷新时关闭动画。两者本质上是同一个参数,所以通常我会在页面状态里存一个bool isFirstBuild,首次构建用默认动画,toggle 数据时把时长设为 0。第一次加载的动画是有效果加分的,柱子的从零生长让数据“活”起来;但用户已经完成首次阅读后的数据刷新动画,往往只是干扰。
7.3 动画时长原则
如果保留动画,建议时长在 300~800ms 之间,并用 Curves.easeOutCubic。太短没感知,太长影响操作。曲线用 easeOutCubic 比默认 easeInOut 更自然,因为柱子生长过程应该是“先快后慢”,最后稳稳停在目标值,观感很好。这个细节用户在大多数情况下不会注意,但实际体验下来差别非常明显。
8. 避坑指南:BarChart 开发中我踩过的 7 个常见坑
8.1 柱子在 x 轴方向不对齐
表现:柱子的 x 位置和 x 轴标签错位。原因通常是你的 BarChartGroupData 的 x 值并不是从 0 开始的连续整数,或者和 getTitlesWidget 里 mapping 的索引不一致。解决办法:统一用连续整数 0、1、2,x 轴标签的 widget 里直接用数组下标索引。
return Text( labels[value.toInt() % labels.length], );额外加一个取模可以防越界,这个习惯我一直在用。
8.2 顶部数值被截断
如果你用上文的透明 Tooltip 方案做常驻数值,图表区域过矮时,文字容易被边界裁掉。解决办法是给 maxY 留余量,maxY 一般是最大数据值的 1.2 倍左右。比如数据最大是 9,maxY 设成 11 或 12。这样柱子顶部的标签就有足够的空间。
8.3 大量柱子导致画面卡顿
默认情况下,fl_chart 在重建时会进行动画插值计算。几十根柱子在性能良好的设备上问题不大,但如果超过 200 根,同时加上透明度渐变和复杂背景,FPS 会明显下降。这里的优化手段:
- 关闭 swapAnimationDuration 动画。
- 减少 gridData 里的横向网格线密度。
- 如果数据维度确实很大,改用 scrollable 的图表方案,或者在进入页面时只展示抽样后的数据。
8.4 空数据显示白屏
当 barGroups 为空数组时,图表不会崩溃,但是画出来一片空白,用户感知就是“页面坏了”。业务上一定要对空数据做兜底:比如在图表位置显示一个空状态提示,或者至少给一个 “暂无数据” 文案。fl_chart 自己是不会主动帮你做这个的。
8.5 常量 x 值导致柱状图重叠
如果直接把 x 设成所有分组都相同,所有柱子会画在同一个 x 位置上,看起来就是几根柱子叠在一起。这种场景如果是有意做“堆叠柱状图”的话,有更规范的方式——用 rodStackItems 并配合 StackedBarChart 的语义。随机设置 x 绝对不是一个好办法。
8.6 中文标签字体宽度差异
x 轴中文标签在窄空间内非常容易互相挤压。默认的 Text widget 没有做截断或换行,两个标签如果靠得近,中文就会和旁边的重叠。建议在 getTitlesWidget 里处理一下:字数多于 4 个字的,换成省略号或者缩小字号,这样图表整体就显得利落干净。
8.7 不同版本 API 差异导致的编译报错
这是排查成本最高的一类坑,尤其是在网上搜索旧代码时。fl_chart 的主要历史 Breaking Change 包括:
| 旧版本写法 | 新版本写法 |
|---|---|
BarChartRodData(fromY: x) | 移除,改用 rodStackItems |
FlTitlesData(show: false) | 使用 AxisTitles(sideTitles: SideTitles(showTitles: false)) |
SideTitleWidget(axisSide: meta.axisSide, child: ...) | 在很多版本里已变为SideTitleWidget(meta: meta, child: ...) |
遇到编译报错时,别急着改逻辑,先看是不是版本 API 变更。最好是去拉对应版本的 changelog,很多报错在 migration guide 里都有写。
9. 一套可以直接抄的通用模板
下面是一个相对完整的业务型模板,包含了坐标轴、格线、Tooltip、常驻数值、点击高亮、双柱对比和空数据兜底。你拿到之后核心要做的事情就是把groupDataList换成自己的数据源。
import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; class SimpleBarChart extends StatelessWidget { const SimpleBarChart({ super.key, required this.dataList, this.selectedX, this.onBarTap, }); final List<({String label, double value})> dataList; final int? selectedX; final ValueChanged<int>? onBarTap; @override Widget build(BuildContext context) { final groups = List.generate(dataList.length, (i) { final data = dataList[i]; return BarChartGroupData( x: i, barRods: [ BarChartRodData( toY: data.value, width: 20, color: selectedX == i ? Colors.orange : Colors.blueAccent, borderRadius: BorderRadius.circular(6), ), ], ); }); if (dataList.isEmpty) { return const SizedBox( height: 280, child: Center(child: Text('暂无数据')), ); } return SizedBox( height: 280, child: BarChart( BarChartData( alignmentSpaceRatio: 0.15, barWidth: 20, maxY: (dataList.map((e) => e.value).reduce((a, b) => a > b ? a : b)) * 1.2, minY: 0, barTouchData: BarTouchData( touchCallback: (event, response) { if (event is FlTapUpEvent && response?.spot != null) { final groupX = response!.spot!.touchedBarGroup!.x; onBarTap?.call(groupX); } }, touchTooltipData: BarTouchTooltipData( getTooltipColor: (_) => Colors.transparent, tooltipPadding: EdgeInsets.zero, tooltipMargin: 6, getTooltipItem: (group, groupIndex, rod, rodIndex) { return BarTooltipItem( rod.toY.toStringAsFixed(1), const TextStyle( color: Colors.black87, fontWeight: FontWeight.w600, fontSize: 13, ), ); }, ), ), gridData: FlGridData( show: true, drawVerticalLine: false, horizontalInterval: 20, getDrawingHorizontalLine: (value) => FlLine( color: Colors.black.withOpacity(0.06), strokeWidth: 1, ), ), borderData: FlBorderData(show: false), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { final index = value.toInt(); if (index < 0 || index >= dataList.length) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.only(top: 8), child: Text( dataList[index].label, style: const TextStyle(fontSize: 12, color: Colors.black54), overflow: TextOverflow.ellipsis, maxLines: 1, ), ); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 36, getTitlesWidget: (value, meta) { if (value.toInt() % 20 == 0 || value == 0) { return Text( value.toInt().toString(), style: const TextStyle(fontSize: 12, color: Colors.black54), ); } return const SizedBox.shrink(); }, ), ), topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)), ), barGroups: groups, swapAnimationDuration: Duration.zero, ), ), ); } }使用示例:
SimpleBarChart( dataList: [ (label: '周一', value: 12.5), (label: '周二', value: 18.0), (label: '周三', value: 9.0), (label: '周四', value: 22.5), (label: '周五', value: 14.0), ], onBarTap: (index) { // 点击第 index 根柱子,做业务处理 }, )9.1 模板里几个容易被忽略的细节
- maxY 的 1.2 倍余量,恰好留出常驻 Tooltip 文本的空间。
- 左侧 y 轴保留 36 的 reservedSize,三位数的 y 标签稳住。
- 空数据直接返回了占位提示,不在图表层硬画。
- 点击事件只响应 FlTapUpEvent,避免移动过程中的误触。
- swapAnimationDuration 设为零,切换数据时不会出现闪烁重播。
9.2 模板扩展方向
- 想要分组柱状图,在 BarChartGroupData 的 barRods 里多加几根 BarChartRodData,并分别配不同颜色和图例。
- 想要横向柱状图,fl_chart 原生对横向支持不友好,建议换 RotatedBox 配合方向配置,或者直接考虑其他图表方案。
- 想要数字保留两位小数,把 getTooltipItem 里的
toStringAsFixed(1)改成toStringAsFixed(2)就行。
10. 参数速查表:BarChart 核心配置一览
| 参数 | 作用 | 注意事项 |
|---|---|---|
barGroups | 柱状图分组数据 | 每个分组一个 x,x 建议连续整数 |
BarChartGroupData.x | 分组在 x 轴的位置 | 与标签映射的索引保持一致 |
BarChartGroupData.barRods | 组内所有柱子 | 多根柱子就是分组柱状图 |
BarChartRodData.toY | 柱子的 y 值(高度) | 可以是小数 |
BarChartRodData.width | 柱子宽度 | 太宽拥挤、太窄单薄 |
BarChartRodData.color/gradient | 柱子颜色/渐变 | 数据展示要克制使用渐变 |
BarChartRodData.borderRadius | 柱子的圆角 | 常用于顶部圆角,底部圆角需要配合 fromY 基准 |
minY/maxY | y 轴显示范围 | 预留 20% 给柱顶标签 |
gridData | 网格线设置 | horizontalInterval 按数据量级调整 |
titlesData | 四周标题 | 顶部和右侧默认关闭 |
barTouchData | 触摸和 Tooltip | Tooltip 可改造为常驻数值 |
swapAnimationDuration | 数据刷新动画时长 | 频繁刷新建议 Duration.zero,首载保留动画 |
alignmentSpaceRatio | 柱间距占比 | 数值越小,间距越小 |
写在最后
fl_chart 的 BarChart 并不难,真正花时间的全在细节调优上。我接手过很多“半成品”图表页面,大多数问题不是功能不会写,而是“差几个像素”和“差一个判断条件”的功夫。回归到最本质的一点:图表是要给人看的,可读性永远优先于炫技。宁可柱子朴实一点,也要保证数据一眼能读准。
我个人在实际项目里的习惯是:把模板稳定下来后,再遇到新需求就只改数据源和局部样式,不再去动底层配置。这套模板帮我省下了大量重复劳动,也减少了很多低级 bug。如果这个分享能帮你少走一段弯路,那这篇文章就没有白写。后面有时间,再把 LineChart 和 PieChart 的坑也整理出来,到时候见。