前言

Flutter是Google开发的开源UI工具包,支持用一套代码构建iOSAndroidWebWindowsmacOSLinux六大平台应用,实现"一次编写,多处运行"。

OpenHarmony是由开放原子开源基金会运营的分布式操作系统,为全场景智能设备提供统一底座,具有多设备支持、模块化设计、分布式能力和开源开放等特性。

Flutter for OpenHarmony技术方案使开发者能够:

  1. 复用Flutter现有代码(Skia渲染引擎、热重载、丰富组件库)
  2. 快速构建符合OpenHarmony规范的UI
  3. 降低多端开发成本
  4. 利用Dart生态插件资源加速生态建设

先看效果

在这里插入图片描述

在这里插入图片描述

在鸿蒙真机 上模拟器上成功运行后的效果
在这里插入图片描述
在这里插入图片描述

目录


页面结构

当前项目的“统计大盘”页面位于 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 只负责拼装与分发回调,组件更容易复用
  • 视觉层级更清晰(信息区属于内容,交互区属于工具条)

使用方式

把标题区放在 SliverToBoxAdapterchild 中即可,保持页面已有的 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.dart
  • lib/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 就够了,必要时再加一个 tooltipsize 之类的少量参数

这样复用的组件会更像“积木”,而不是需要反复微调的“半成品布局”。


常见问题排查

  • 切换范围后数据不刷新:优先检查 _onRangeChanged 是否触发了数据加载,以及加载完成后是否调用了 setState 更新 _trend/_bars
  • 选中态不明显:优先检查 _RangeTabsfillColor/selectedColor 与背景对比度,必要时降低玻璃底透明度提升可读性
  • 点击不灵敏:检查控件外层是否有过多 Padding 导致点击区域变小,或是否被 IgnorePointer/AbsorbPointer 误包裹
  • 水波纹不显示:检查是否缺少 Material 祖先,或外层裁剪导致效果被完全切掉
  • 页面看起来“乱”:通常是边距/圆角/字体层级不一致,先统一这些基础规范,再做装饰效果会更稳

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐