Flutter for OpenHarmony 实战:折线图、柱状图展示统计数据
是Google开发的开源UI工具包,支持用一套代码构建和六大平台应用,实现"一次编写,多处运行"。是由开放原子开源基金会运营的分布式操作系统,为全场景智能设备提供统一底座,具有多设备支持、模块化设计、分布式能力和开源开放等特性。
前言
Flutter是Google开发的开源UI工具包,支持用一套代码构建iOS、Android、Web、Windows、macOS和Linux六大平台应用,实现"一次编写,多处运行"。
OpenHarmony是由开放原子开源基金会运营的分布式操作系统,为全场景智能设备提供统一底座,具有多设备支持、模块化设计、分布式能力和开源开放等特性。
Flutter for OpenHarmony技术方案使开发者能够:
- 复用Flutter现有代码(Skia渲染引擎、热重载、丰富组件库)
- 快速构建符合OpenHarmony规范的UI
- 降低多端开发成本
- 利用Dart生态插件资源加速生态建设
先看效果


在鸿蒙真机 上模拟器上成功运行后的效果

目录
- 页面结构
- 页面:StatsDashboardPage
- 标题区:HeaderTitle
- 时间范围切换:_RangeTabs
- 刷新按钮:_GlowIconButton
- 风格底座:NeonBackground / GlassCard
- 状态与数据流
- 布局与间距规范
- 组件抽取与复用建议
- 常见问题排查
页面结构
当前项目的“统计大盘”页面位于 lib/pages/stats_dashboard_page.dart,整体采用 CustomScrollView + Sliver 的滚动体系。页面从上到下通常由三类内容组成:
- 信息区:标题与当前范围文案(例如“近七天/近三十天”)
- 交互区:范围切换(“日/周/月”)与刷新
- 内容区:KPI 卡片、折线图卡片、柱状图卡片等
把信息展示与交互控件拆开后,页面会更好维护:样式调整不影响数据逻辑,业务状态变更也不会把 UI 拼装写得很碎。更重要的是,这种拆分方式能让你在后续扩展“更多筛选项 / 更多统计维度 / 新的图表卡片”时,不需要推翻原有结构,只要在内容区追加新的卡片即可。
建议把信息区与交互区看成“页面上半部的统一控制面板”:它只负责展示当前选择的范围、提供切换与刷新入口;真正的数据渲染放在内容区卡片里,避免在头部写太多业务逻辑,导致难以复用或调整排版。
页面:StatsDashboardPage
文件位置
lib/pages/stats_dashboard_page.dart
作用
StatsDashboardPage 是页面的“容器层”。它主要负责:
- 滚动骨架:用
CustomScrollView组织 Sliver 列表 - 状态管理:维护
_range(日/周/月)、图表数据_trend/_bars,以及图表交互选中索引 - 事件分发:把
_onRangeChanged与_onRefresh传给子组件,让交互组件不直接读写业务数据
为了让页面长期可维护,容器层尽量只做“拼装 + 状态协调”,把样式细节与交互手感沉到更小的组件里(例如 _RangeTabs、_GlowIconButton)。这样当你想替换某个控件的视觉方案时,只要改它自身即可,不会牵连整个页面结构。
常见组合方式
把标题 + 文案 + 交互控件放在一个普通 Sliver(例如 SliverToBoxAdapter)中即可,示例只展示结构与调用方式:
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 10, 16, 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('统计大盘', style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 6),
Text(_rangeLabel(_range), style: Theme.of(context).textTheme.bodySmall),
const SizedBox(height: 10),
Row(
children: [
_RangeTabs(value: _range, onChanged: _onRangeChanged),
const SizedBox(width: 10),
_GlowIconButton(icon: Icons.refresh_rounded, onPressed: _onRefresh),
],
),
],
),
),
),
如果你希望页面在不同尺寸设备上保持一致观感,可以把这段“头部区域”抽成一个私有 Widget(例如 _HeaderSection),并只向它传入当前范围与回调。这种做法不会改变功能,但会显著减少页面 build 方法里堆叠的布局代码,让内容区卡片更容易阅读与维护。
标题区:HeaderTitle
作用
标题区只做信息展示:页面标题 + 当前范围文案(例如 _rangeLabel(_range))。它不承担数据请求或复杂交互,建议保持布局轻量,方便统一调整页面的排版节奏。
标题区建议包含两类信息:
- 静态标题:例如“统计大盘”,用于建立页面定位
- 动态副标题:例如当前范围文案、最后刷新时间(如果你后续需要),用于解释“现在看到的数据是什么口径”
标题区的核心原则是“可读性优先”:字号、字重与对比度要确保在霓虹渐变背景上也能清晰识别;间距上尽量与下方卡片保持一致的左对齐与上下留白,避免出现头部很挤、内容区很松的割裂感。
建议拆分原因
如果把标题与交互控件全部塞进同一个 _Header,后续要改样式或增加交互时容易互相牵连。拆成“标题区”和“交互区”后:
- 文案展示与交互逻辑解耦,改样式时互不影响
StatsDashboardPage只负责拼装与分发回调,组件更容易复用- 视觉层级更清晰(信息区属于内容,交互区属于工具条)
使用方式
把标题区放在 SliverToBoxAdapter 的 child 中即可,保持页面已有的 padding 与字体样式,避免与卡片区域的左右边距不一致。
时间范围切换:_RangeTabs
文件位置
lib/pages/stats_dashboard_page.dart(当前为页面内私有组件)
作用
它把 ToggleButtons 包装成“三段切换”。职责是:
- 根据
TimeRange计算isSelected - 将点击索引映射回
TimeRange并回调 - 固化尺寸、圆角、颜色,保证在不同内容区间滚动时视觉稳定
在体验上,范围切换属于高频操作,重点不在“炫酷”,而在“明确”和“稳”:用户点下去应该立刻知道自己切到了哪个范围,并且选中态在不同背景、不同亮度下都清晰可见。你可以优先保证选中态对比度,其次再做轻量的玻璃感与发光效果。
关键实现点(你在改样式时会用到)
constraints: BoxConstraints(minHeight: 36, minWidth: 46):保证每段都有可点面积fillColor/selectedColor/color:决定选中与未选中对比度borderWidth: 0:外框交给父容器(玻璃底)去表达,更干净
如果你发现某些机型上点击不够跟手,优先检查可点击区域是否被额外的 Padding 压缩,以及 ToggleButtons 外层是否被 ClipRRect/BackdropFilter 影响了水波纹呈现。一般来说,让点击区域更“厚实”(高度更稳定、左右留白更一致)比增加动画更有效。
使用方式
_RangeTabs(
value: _range,
onChanged: _onRangeChanged,
)
刷新按钮:_GlowIconButton
文件位置
lib/pages/stats_dashboard_page.dart(当前为页面内私有组件)
作用
刷新按钮的设计目标是“存在感明显,但不抢主内容”。它做的事情很简单:
- 用
InkResponse接点击 - 用半透明底 + 边框做玻璃感
- 用轻微 glow 阴影做层级提示
在行为上,刷新按钮建议只触发“重新拉取当前范围的数据”,不要隐式改变范围或重置用户在图表上的选中状态(除非这是你明确的产品行为)。保持刷新语义单一,可以避免用户误解:他点刷新只是为了更新数据,而不是为了“回到默认视图”。
参数说明
icon:按钮图标(例如Icons.refresh_rounded)onPressed:点击回调
使用方式
_GlowIconButton(
icon: Icons.refresh_rounded,
onPressed: _onRefresh,
)
风格底座:NeonBackground / GlassCard
文件位置
lib/widgets/neon_background.dartlib/widgets/glass_card.dart
作用
这两个组件决定了页面的统一观感:
- 背景偏霓虹渐变(NeonBackground)
- 内容卡片偏玻璃拟态(GlassCard)
页面里新增任何“工具条/交互块/卡片”时,建议沿用同一套玻璃感(半透明底、轻边框、圆角一致),避免出现与现有卡片不一致的厚重纯色块或过强阴影。
更具体一点:
- 如果某个区域需要“承载操作”(例如范围切换、刷新),建议用 GlassCard 的风格做一个轻量容器:让控件有边界但不厚重,既能突出交互区,又不会抢走图表卡片的注意力。
- 如果某个区域需要“承载信息”(例如 KPI 数字、图表),建议让卡片背景更克制:透明度稍低、边框更细,保证文字与曲线的可读性优先。
- 背景 NeonBackground 的霓虹色块本质上会引入复杂底色,所有前景文字都需要考虑对比度。遇到可读性问题,优先通过文字颜色/阴影/背景透明度调整解决,而不是堆更多装饰。
状态与数据流
统计大盘页面通常会同时管理两类状态:业务状态与交互状态。
业务状态以“范围”为中心展开:
_range决定了查询口径(日/周/月)以及标题区的范围文案_trend/_bars等数据集合由_range驱动更新,并作为图表与卡片的输入_onRangeChanged的职责是更新_range,并触发对应的数据刷新(可以是立即刷新,也可以是标记为脏数据后按需刷新)
交互状态用于提升可用性:
- 图表上的选中点/高亮索引属于交互状态,它反映用户当前关注的具体数据点
- 刷新通常只更新数据,不建议顺带清空交互状态;如果数据量变化导致索引越界,再做兜底重置即可
为了避免“切换范围后 UI 闪烁”或“重复请求”,建议把数据刷新逻辑集中在两个入口:
- 范围变更:
_onRangeChanged - 手动刷新:
_onRefresh
这样页面中其他地方(例如图表手势、卡片点击)只更新交互状态,不直接触发数据请求,整个页面会更可控。
布局与间距规范
页面观感是否高级,很多时候取决于“间距是否一致”。建议保持以下几条简单规则:
- 页面左右边距固定:常用 16,与卡片保持一致的对齐线
- 模块之间的纵向间距固定:标题与副标题、头部与卡片、卡片与卡片之间尽量复用同一套间距(例如 6 / 10 / 12 这样的阶梯)
- 交互控件高度稳定:范围切换与刷新按钮不要随着内容变化出现高度跳动,避免视觉抖动
- 小组件的视觉边界明确:玻璃卡片边框与圆角统一,阴影要克制,优先通过对比度而不是强阴影来分层
当你新增一个区域时,可以先对照现有卡片的边距与圆角,再决定新区域的 padding 与约束,这样整体“像同一个设计系统”而不是拼出来的。
组件抽取与复用建议
当前 _RangeTabs 与 _GlowIconButton 都在页面文件内作为私有组件存在,这对快速迭代很友好;当你确认样式稳定后,可以按“复用收益”逐步抽取:
- 跨页面复用的抽取到
lib/widgets/:例如玻璃按钮、统一风格的标签/开关 - 只在统计页面复用的保留为私有:例如带有
TimeRange语义的切换控件
抽取时建议只暴露“语义参数”,避免暴露太多样式参数导致调用方到处传颜色/圆角:
_RangeTabs暴露value/onChanged就够了,样式内部自洽_GlowIconButton暴露icon/onPressed就够了,必要时再加一个tooltip或size之类的少量参数
这样复用的组件会更像“积木”,而不是需要反复微调的“半成品布局”。
常见问题排查
- 切换范围后数据不刷新:优先检查
_onRangeChanged是否触发了数据加载,以及加载完成后是否调用了setState更新_trend/_bars - 选中态不明显:优先检查
_RangeTabs的fillColor/selectedColor与背景对比度,必要时降低玻璃底透明度提升可读性 - 点击不灵敏:检查控件外层是否有过多
Padding导致点击区域变小,或是否被IgnorePointer/AbsorbPointer误包裹 - 水波纹不显示:检查是否缺少
Material祖先,或外层裁剪导致效果被完全切掉 - 页面看起来“乱”:通常是边距/圆角/字体层级不一致,先统一这些基础规范,再做装饰效果会更稳
欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)