flutter_for_openharmony逆向思维训练app实战+时间分析实现
·

时间分析页的目标很明确:
- 给用户一个“学习时间”的总览(今日/本周/本月/总计)
- 再用一条折线图展示一周的学习趋势
这个页面属于 进度统计 模块,它更偏数据看板,而不是做题训练。
因此“可读性”和“信息层级”是最重要的设计目标。
本文涉及文件
lib/feature_pages.dartlib/app.dartlib/main.dart
1. 入口在哪里:从“进度统计”进入
时间分析属于 ProgressStatsPage(进度统计)里的一个入口。
入口页负责 push 到 TimeAnalysisPage,核心导航代码示例:
// 进度统计页中跳转至时间分析页的按钮点击事件
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (_) => const TimeAnalysisPage()),
);
},
child: const Text('查看时间分析'),
)
- 采用
MaterialPageRoute保证路由过渡动画符合平台规范 - 跳转时传入
const TimeAnalysisPage()利用常量构造器优化性能 - 按钮文案“查看时间分析”语义清晰,符合用户操作预期
你的项目整体导航结构一直保持一致:
- 聚合页负责入口
- 子页负责实现
2. TimeAnalysisPage
下面这段实现来自项目 lib/feature_pages.dart,
2.1 页面基础结构定义
class TimeAnalysisPage extends StatelessWidget {
const TimeAnalysisPage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('时间分析')),
body: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
- 核心设计点1:使用
StatelessWidget适配纯展示型统计页,无状态变更时性能更优 - 核心设计点2:
16.w为响应式间距单位,适配不同屏幕宽度的鸿蒙/Flutter设备 - 核心设计点3:
Scaffold + AppBar是Flutter标准页面骨架,保证导航栏一致性
2.2 页面标题与间距控制
Text('学习时间分析',
style: TextStyle(
fontSize: 20.sp,
fontWeight: FontWeight.bold
)
),
SizedBox(height: 24.h),
- 标题字号
20.sp采用响应式单位,比固定px更适配多尺寸设备 SizedBox(height: 24.h)明确分隔标题与下方卡片,视觉呼吸感更佳- 标题加粗处理,强化页面核心主题的视觉层级
2.3 学习时间总览卡片容器
Card(
elevation: 2, // 补充阴影提升卡片层次感
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.w),
),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
children: [
- 新增
elevation: 2给卡片添加轻微阴影,区分卡片与背景层 - 新增
shape属性设置圆角,符合移动端UI设计美学(8.w适配不同屏幕) Card组件包裹统计项,天然区分“总览数据”与“趋势图表”模块
2.4 时间统计项复用方法调用
_buildTimeItem('今日学习', '2小时15分钟'),
_buildTimeItem('本周学习', '12小时30分钟'),
_buildTimeItem('本月学习', '45小时20分钟'),
_buildTimeItem('总学习时间', '156小时45分钟'),
- 复用
_buildTimeItem方法,避免重复编写4次相同布局代码 - 统计维度覆盖“今日-本周-本月-总计”,满足用户不同时间粒度的查看需求
- 时间字符串格式统一为“X小时Y分钟”,降低用户理解成本
2.5 卡片闭合与趋势图标题
],
),
),
),
SizedBox(height: 24.h),
Text('学习趋势',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold
)
),
SizedBox(height: 16.h),
- 卡片闭合后再次使用
SizedBox分隔,保持模块间间距统一(24.h) - 趋势图标题字号
18.sp,略小于主标题,形成二级视觉层级 - 趋势图标题与图表间间距16.h,比模块间距更小,视觉更紧凑
2.6 折线图核心布局与配置
Expanded(
child: LineChart(
LineChartData(
lineBarsData: [
LineChartBarData(
spots: List.generate(7, (i) =>
FlSpot(i.toDouble(), (sin(i * 0.8) + 2) * 30)
),
isCurved: true,
color: Colors.orange,
barWidth: 3, // 补充线条宽度
dotData: FlDotData(show: true), // 显示数据点
),
],
Expanded让图表占据剩余空间,避免因屏幕尺寸不同导致图表高度异常- 新增
barWidth: 3增加线条宽度,提升图表可读性 - 新增
dotData: FlDotData(show: true)显示数据点,用户可清晰看到每日数值 sin函数生成演示数据,波动范围可控,模拟真实学习时长变化
2.7 图表坐标轴标题配置
titlesData: FlTitlesData(
bottomTitles: AxisTitles(sideTitles: SideTitles(
showTitles: true,
reservedSize: 24, // 预留标题空间
getTitlesWidget: (value, meta) =>
Text(['一', '二', '三', '四', '五', '六', '日'][value.toInt()]),
)),
leftTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
),
),
),
- 新增
reservedSize: 24为底部星期标题预留空间,避免文字被截断 - 隐藏左侧Y轴标题(
leftTitles: showTitles: false),简化图表视觉,聚焦趋势 getTitlesWidget映射索引到中文星期,符合中文用户使用习惯
2.8 页面布局闭合
],
),
),
);
}
- 所有嵌套布局逐层闭合,保证代码语法正确性
- Column、Padding、Scaffold 等组件嵌套逻辑清晰,符合Flutter布局规范
2.9 时间项复用方法实现
Widget _buildTimeItem(String label, String time) {
return Padding(
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: TextStyle(fontSize: 16.sp)),
Text(time,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: Colors.blueAccent, // 补充时间文字颜色
)
),
],
),
);
}
}
- 核心复用逻辑:接收
label和time参数,适配不同统计项 MainAxisAlignment.spaceBetween让标签左对齐、时间右对齐,布局整洁- 新增
color: Colors.blueAccent给时间文字上色,强化核心信息视觉焦点 - 垂直间距
8.h保证每行统计项间距均匀,避免拥挤
3. 为什么用 StatelessWidget:这是展示型统计页
你把时间分析写成 StatelessWidget,补充核心原因:
class TimeAnalysisPage extends StatefulWidget {
const TimeAnalysisPage({super.key});
State<TimeAnalysisPage> createState() => _TimeAnalysisPageState();
}
class _TimeAnalysisPageState extends State<TimeAnalysisPage> {
Widget build(BuildContext context) {
return const Scaffold(body: Text('无状态变更时无需Stateful'));
}
}
- 核心原因1:当前代码中总览数据是写死的字符串,无动态变更需求
- 核心原因2:趋势数据是用
sin生成的演示数据,无需实时更新 - 核心原因3:页面无筛选、切换等交互开关,无 setState 调用场景
- 性能优势:StatelessWidget 无需维护状态对象,重建时开销更小
如果未来你要接入真实学习记录(例如按天累计学习时长),那时这页可能会变成:
- Stateful(自己拉取/聚合数据)
- 或者仍然 Stateless(由上层把统计结果注入)
4. 页面结构:上半部分总览,下半部分趋势
你用一个 Column 把页面拆成两块,补充布局优化细节:
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
],
)
- 结构优势1:先给用户“概览结论”(总览卡片),符合用户“先看结果再看细节”的认知习惯
- 结构优势2:再给用户“变化趋势”(折线图),满足深度查看需求
- 布局兜底:新增
mainAxisSize: MainAxisSize.min防止Column无限延伸导致溢出 - 对齐优化:
crossAxisAlignment: CrossAxisAlignment.start让标题左对齐,更符合中文阅读习惯
5. 总览卡片:_buildTimeItem 抽取重复 UI
你没有重复写四个 Row,而是抽了一个方法,补充复用价值:
Padding(
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('今日学习', style: TextStyle(fontSize: 16.sp)),
Text('2小时15分钟', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
],
),
),
Padding(
padding: EdgeInsets.symmetric(vertical: 8.h),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('本周学习', style: TextStyle(fontSize: 16.sp)),
Text('12小时30分钟', style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold)),
],
),
),
_buildTimeItem 方法内部做了三件事:
- 用 Padding 给每一行留出上下间距
- 用 Row 做左右布局
- 左侧 label 普通字重,右侧 time 加粗且上色,强化核心信息
mainAxisAlignment: MainAxisAlignment.spaceBetween
这会让每一行像“数据表项”,非常适合统计页。
6. 为什么右侧 time 要加粗
你写了加粗样式,补充视觉设计原理:
Text(label, style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.normal,
color: Colors.grey[600],
)),
Text(time, style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: Colors.blueAccent,
)),
- 设计原理1:对比原则——加粗形成视觉焦点,用户扫一眼就能抓住关键数字
- 设计原理2:层级原则——label 是说明(次要信息),time 是核心信息
- 强化优化:新增灰色调给label、蓝色调给time,进一步拉大视觉层级
- 一致性:所有time项统一加粗+上色,保持视觉风格一致
7. 趋势图:LineChart + 7 天数据
你用 LineChart 展示“学习趋势”,补充数据生成逻辑解析:
List<FlSpot> generateLearningSpots() {
return List.generate(7, (i) {
final sinValue = sin(i * 0.8);
final positiveValue = sinValue + 2;
final minutes = positiveValue * 30;
return FlSpot(i.toDouble(), minutes);
});
}
- 数据逻辑1:
sin(i * 0.8)生成波动,0.8的系数让7天内波动2~3次,符合真实学习规律 - 数据逻辑2:
+ 2把值抬高,避免出现负数学习时长(不符合业务逻辑) - 数据逻辑3:
* 30把数值放大到30~90分钟范围,映射到合理的单次学习时长 - 复用思路:抽取
generateLearningSpots方法,方便后续替换为真实数据
这个处理方式和你项目里“时间序列”页的思路是一致的:
- 用可解释、可控的数学函数生成演示数据
- 数据范围贴合业务场景,避免无意义的数值
8. isCurved:用平滑曲线强调趋势感
你设置了 isCurved: true,补充视觉效果对比:
LineChartBarData(
isCurved: true,
// isCurved: false,
curveSmoothness: 0.6,
),
- 视觉效果1:平滑曲线更符合“趋势”的视觉认知,用户更容易感知整体走势
- 视觉效果2:折线的尖角会分散注意力,让用户过度关注单日数值变化
- 优化补充:
curveSmoothness: 0.6控制曲线平滑度,避免过度平滑导致失真 - 业务适配:训练类App的统计模块,用户核心诉求是“整体学习规律”而非“单日精确值”
9. bottomTitles:用中文星期标注 x 轴
Widget buildWeekdayTitle(double value, TitleMeta meta) {
final index = value.toInt();
if (index < 0 || index >= 7) return const SizedBox();
final weekdays = ['一', '二', '三', '四', '五', '六', '日'];
return Text(weekdays[index]);
}
- 核心价值1:把0~6的数字映射为中文星期,提升图表可读性
- 核心价值2:只配置底部标题,避免多轴标签干扰视觉焦点
- 安全优化:新增边界处理,防止value超出0~6范围导致数组越界
- 复用优化:抽取
buildWeekdayTitle方法,方便后续扩展多语言支持
需要注意的隐含约束是:
- value 的范围必须在 0~6
你生成的 spots 也正好是 0~6,所以匹配。
10. Expanded:确保图表有足够空间
你把 LineChart 放进了 Expanded,补充布局对比:
Container(
height: 200,
child: LineChart(...),
)
Expanded(
flex: 1,
child: LineChart(...),
)
- 布局优势1:避免图表高度固定导致的小屏挤压/大屏留白问题
- 布局优势2:在不同屏幕比例(如16:9/18:9)下保持布局稳定
- 业务适配:统计页中图表是核心内容,Expanded保证其优先级
- 扩展灵活:可通过
flex参数调整图表与其他模块的空间占比
11. 如何接入真实数据:从“每日学习记录”聚合
你现在的展示数据是字符串和 sin,补充真实数据接入示例:
class LearningTimeCalculator {
static List<int> getDailyLearningMinutes() {
return [45, 60, 30, 90, 75, 60, 80];
}
static String calculateToday() {
final today = getDailyLearningMinutes().last;
return '${today ~/ 60}小时${today % 60}分钟';
}
static String calculateWeek() {
final total = getDailyLearningMinutes().reduce((a, b) => a + b);
return '${total ~/ 60}小时${total % 60}分钟';
}
static List<FlSpot> convertToSpots() {
return getDailyLearningMinutes().asMap().entries.map((e) {
return FlSpot(e.key.toDouble(), e.value.toDouble());
}).toList();
}
}
- 接入步骤1:定义数据模型,存储每日学习时长(分钟为单位,便于计算)
- 接入步骤2:实现聚合方法,分别计算今日/本周/本月/总计时长
- 接入步骤3:统一格式转换,将分钟数转为“X小时Y分钟”的展示字符串
- 接入步骤4:映射图表数据,将每日分钟数转为FlSpot对象
- 口径统一:所有统计维度使用分钟为底层单位,展示时统一格式化
12. 小结:时间分析实现的关键点
- 总览清晰:Card + 4 行时间统计,新增阴影/圆角提升视觉层次
- 复用到位:_buildTimeItem 抽取重复布局,减少冗余代码
- 趋势可读:LineChart + 中文星期标签,新增数据点/线条宽度优化
- 布局稳定:趋势图用 Expanded,适配不同屏幕尺寸
- 扩展灵活:预留真实数据接入接口,便于后续业务迭代
到这里,这篇文章已经把你项目里的“时间分析”页面从代码结构到扩展方向讲清楚了.
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)