Flutter for OpenHarmony艺考真题题库+成绩分析实现

成绩分析是艺考学习应用中的核心功能,它能够帮助用户深入了解自己的学习表现。今天我们将详细介绍如何构建一个功能完善的成绩分析页面,包含成绩趋势、分数分布、科目对比等功能。
成绩分析架构
成绩分析页面采用StatefulWidget设计,核心设计思路如下:
- 支持多时间维度切换,满足用户不同周期的成绩复盘需求;
- 页面采用可滚动布局,适配多图表展示场景;
- 组件化拆分各分析模块,提升代码复用性和维护性。
class ScoreAnalysisPage extends StatefulWidget {
const ScoreAnalysisPage({Key? key}) : super(key: key);
State<ScoreAnalysisPage> createState() => _ScoreAnalysisPageState();
}
上述代码是页面的基础结构定义,说明:
- 继承StatefulWidget以支持状态更新(如时间维度切换);
- 构造函数添加const修饰,提升初始化性能;
- 分离State类,符合Flutter的状态管理最佳实践。
class _ScoreAnalysisPageState extends State<ScoreAnalysisPage> {
String selectedPeriod = '本月';
final List<String> periods = ['本周', '本月', '三个月', '全部'];
}
状态类核心变量说明:
- selectedPeriod:默认选中"本月",贴合用户高频查看习惯;
- periods数组定义所有可选时间维度,后续可扩展更多周期(如半年);
- 变量私有化,仅在当前State类内修改,保证数据安全性。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('成绩分析'),
backgroundColor: Colors.blue,
),
);
}
页面骨架搭建要点:
- 使用Scaffold作为根布局,提供标准的AppBar+Body结构;
- AppBar设置固定标题和主题色,保持应用视觉统一性;
- 背景色选用蓝色系,符合教育类应用的视觉调性。
actions: [
PopupMenuButton<String>(
onSelected: (value) {
setState(() {
selectedPeriod = value;
});
},
),
],
时间维度切换功能实现:
- 借助PopupMenuButton实现下拉选择,交互简洁;
- onSelected回调中调用setState,触发页面重新构建;
- 无额外弹窗,符合移动端轻量交互设计原则。
itemBuilder: (context) => periods.map((period) {
return PopupMenuItem(
value: period,
child: Text(period),
);
}).toList(),
下拉菜单构建逻辑:
- 遍历periods数组生成选项,避免重复代码;
- value与显示文本一致,简化数据映射;
- 支持动态扩展时间维度,无需修改UI逻辑。
body: SingleChildScrollView(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildScoreTrend(),
SizedBox(height: 24.h),
],
),
),
页面主体布局设计:
- SingleChildScrollView包裹,适配小屏设备纵向滚动;
- 使用flutter_screenutil的w/h单位,保证多设备适配;
- Column按顺序排列各分析模块,间距24h提升视觉呼吸感;
- 先加载成绩趋势模块,符合用户"先看整体趋势"的认知习惯。
_buildScoreDistribution(),
SizedBox(height: 24.h),
_buildSubjectComparison(),
SizedBox(height: 24.h),
_buildScoreRanking(),
模块加载顺序设计逻辑:
- 从整体趋势→分数分布→科目对比→排名,符合"从宏观到微观"的分析逻辑;
- 每个模块间保留统一间距,保证页面布局规整;
- 各模块封装为独立方法,降低代码耦合度。
成绩趋势图表
成绩趋势图表是核心可视化模块,设计目标:
- 直观展示分数波动,支持多周期数据切换;
- 包含关键指标统计,快速提炼核心信息;
- 视觉风格轻量化,避免过度设计干扰数据阅读。
Widget _buildScoreTrend() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
);
}
趋势图表容器样式设计:
- 白色背景+圆角,区分模块边界;
- 16w内边距,保证内容与容器间距合理;
- 使用r单位适配圆角,避免不同设备圆角比例失调。
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.1),
blurRadius: 10,
offset: const Offset(0, 4),
),
],
阴影效果优化:
- 低透明度灰色阴影,提升层次感但不突兀;
- 模糊半径10+向下偏移4,模拟轻微悬浮效果;
- 仅底部阴影,符合移动端卡片设计主流风格。
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('成绩趋势', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
Text(selectedPeriod, style: TextStyle(fontSize: 14.sp, color: Colors.grey[600])),
],
),
],
),
模块标题栏设计:
- 左侧主标题加粗,突出模块名称;
- 右侧显示当前选中周期,强化数据上下文;
- 字号区分(18sp/14sp),建立视觉层级。
SizedBox(height: 16.h),
SizedBox(
height: 200.h,
child: LineChart(LineChartData(gridData: FlGridData(
show: true,
drawVerticalLine: false,
horizontalInterval: 20,
))),
),
折线图基础配置:
- 固定高度200h,保证图表展示区域足够且不占屏过多;
- 仅显示水平网格线,避免垂直网格线干扰趋势阅读;
- 水平网格线间隔20分,贴合100分制的分数区间划分。
getDrawingHorizontalLine: (value) {
return FlLine(
color: Colors.grey[300],
strokeWidth: 1,
);
},
网格线样式优化:
- 浅灰色+1px宽度,弱化网格线视觉权重;
- 统一的网格线样式,保证图表视觉一致性;
- 仅渲染有数据的网格线,减少性能消耗。
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: 20,
getTitlesWidget: (value, meta) {
return Text(value.toInt().toString(), style: TextStyle(fontSize: 12.sp));
},
),
),
),
左侧坐标轴配置:
- 显示分数刻度,间隔20分,与网格线对齐;
- 仅显示整数分数,简化视觉信息;
- 字号12sp,保证可读性且不占用过多空间。
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
冗余坐标轴隐藏:
- 隐藏右侧/顶部坐标轴,减少视觉干扰;
- 仅保留核心的左侧(分数)和底部(次数)坐标轴;
- 符合"极简数据可视化"的设计原则。
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
final labels = ['第1次', '第2次', '第3次', '第4次', '第5次'];
return Text(labels[value.toInt()], style: TextStyle(fontSize: 10.sp));
},
),
),
底部坐标轴配置:
- 按考试次数标注,贴合用户刷题/考试的行为逻辑;
- 字号10sp,小于左侧分数刻度,突出核心数据;
- 数组化管理标签,便于后续动态更新。
borderData: FlBorderData(show: false),
lineBarsData: [
LineChartBarData(
spots: [const FlSpot(0, 75), const FlSpot(1, 82)],
isCurved: true,
color: Colors.blue,
barWidth: 3.w,
),
],
趋势线核心配置:
- 隐藏图表边框,强化轻量化视觉;
- 曲线样式(isCurved: true),让趋势更流畅;
- 蓝色主色调,与AppBar颜色统一;
- 3w线宽,保证线条清晰但不突兀。
spots: [const FlSpot(2, 78), const FlSpot(3, 85), const FlSpot(4, 92)],
趋势数据示例说明:
- 模拟5次考试分数,呈现稳步上升趋势;
- 分数区间75-92,符合艺考真题得分的常见范围;
- 数据点均匀分布,便于观察提升幅度。
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 5.r,
color: Colors.blue,
strokeWidth: 2.w,
strokeColor: Colors.white,
);
},
),
数据点样式设计:
- 显示实心圆点+白色描边,提升辨识度;
- 5r半径+2w描边,保证点击交互区域足够;
- 颜色与趋势线一致,视觉统一。
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildTrendItem('最高分', '92', Colors.green),
],
),
趋势指标栏布局:
- 16h间距,分隔图表与指标栏;
- 水平均匀分布,保证视觉平衡;
- 第一个指标展示最高分,绿色标识正向数据。
_buildTrendItem('最低分', '75', Colors.red),
_buildTrendItem('平均分', '82.4', Colors.blue),
_buildTrendItem('提升', '+17分', Colors.purple),
核心指标设计逻辑:
- 最低分用红色,强化警示性;
- 平均分用主题蓝,贴合整体风格;
- 提升幅度用紫色,突出进步成果;
- 指标覆盖极值、均值、增量,维度完整。
趋势统计项
趋势统计项是成绩趋势模块的补充,设计要点:
- 极简卡片样式,聚焦数据展示;
- 颜色区分指标类型,强化视觉识别;
- 适配不同屏幕宽度,保证排版整齐。
Widget _buildTrendItem(String label, String value, Color color) {
return Column(
children: [
Text(value, style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold, color: color)),
SizedBox(height: 4.h),
],
);
}
统计项核心布局:
- 数值在上、标签在下,符合用户阅读习惯;
- 数值字号18sp加粗,突出核心信息;
- 4h间距,保证数值与标签不重叠。
Text(
label,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey[600],
),
),
标签样式设计:
- 浅灰色+12sp字号,弱化次要信息;
- 统一的标签样式,保证视觉一致性;
- 颜色选用灰色系,避免与数值颜色冲突。
分数分布图表
分数分布图表用于分析用户得分区间,设计目标:
- 展示不同分数段的题目数量,定位薄弱区间;
- 柱状图样式直观,符合分数分布的可视化习惯;
- 颜色区分不同分数段,强化视觉引导。
Widget _buildScoreDistribution() {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4))],
),
);
}
分布图表容器设计:
- 复用趋势图表的容器样式,保证页面视觉统一;
- 阴影效果与卡片风格一致,提升整体质感;
- 圆角+内边距,符合移动端卡片设计规范。
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('分数分布', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 16.h),
],
),
分布图表标题栏:
- 标题样式与趋势模块一致,建立视觉统一;
- 16h间距,分隔标题与图表区域;
- 左对齐布局,符合阅读习惯。
SizedBox(
height: 200.h,
child: BarChart(
BarChartData(
gridData: FlGridData(show: false),
titlesData: FlTitlesData(
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
return Text(value.toInt().toString(), style: TextStyle(fontSize: 12.sp));
},
),
),
),
),
),
),
柱状图基础配置:
- 隐藏网格线,避免与柱状条重叠;
- 左侧坐标轴显示题目数量,12sp字号保证可读性;
- 固定高度200h,与趋势图表保持一致。
rightTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
getTitlesWidget: (value, meta) {
final ranges = ['60-70', '70-80', '80-90', '90-100'];
return Text(ranges[value.toInt()], style: TextStyle(fontSize: 10.sp));
},
),
),
底部坐标轴配置:
- 按10分区间划分,贴合100分制评分逻辑;
- 仅显示4个核心区间,聚焦关键得分段;
- 10sp字号,保证标签清晰且不占空间。
borderData: FlBorderData(show: false),
barGroups: [
BarChartGroupData(x: 0, barRods: [
BarChartRodData(toY: 2, color: Colors.red, width: 20.w),
]),
],
60-70分区间配置:
- 红色标识低分区间,强化警示;
- 宽度20w,保证柱状条足够宽且间距合理;
- toY:2表示该区间有2道题,数据贴合艺考真题数量。
BarChartGroupData(x: 1, barRods: [
BarChartRodData(toY: 5, color: Colors.orange, width: 20.w),
]),
70-80分区间配置:
- 橙色标识中等偏下区间;
- 5道题数量,反映该区间题目占比适中;
- 与低分区间颜色区分,视觉层级清晰。
BarChartGroupData(x: 2, barRods: [
BarChartRodData(toY: 8, color: Colors.blue, width: 20.w),
]),
80-90分区间配置:
- 主题蓝标识核心得分区间;
- 8道题数量,反映用户主要得分区间;
- 数量最多,突出该区间的重要性。
BarChartGroupData(x: 3, barRods: [
BarChartRodData(toY: 3, color: Colors.green, width: 20.w),
]),
90-100分区间配置:
- 绿色标识高分区间,强化正向反馈;
- 3道题数量,符合艺考难题占比规律;
- 颜色与最高分指标一致,视觉统一。
科目对比分析
科目对比分析是艺考特色模块,设计目标:
- 对比用户得分与平均分,定位优势/薄弱科目;
- 可视化进度条样式,直观展示分数差距;
- 覆盖核心艺考科目,贴合用户学习场景。
Widget _buildSubjectComparison() {
final subjects = [
{'name': '美术基础', 'score': 88, 'avg': 75},
{'name': '音乐理论', 'score': 92, 'avg': 78},
];
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4))],
),
);
}
科目数据初始化:
- 包含美术基础、音乐理论等核心艺考科目;
- 每个科目存储名称、用户得分、平均分,维度完整;
- 容器样式复用前序模块,保证视觉统一。
subjects.add({'name': '舞蹈基础', 'score': 76, 'avg': 72});
subjects.add({'name': '播音主持', 'score': 85, 'avg': 80});
科目数据补充:
- 覆盖四大艺考方向,满足不同用户需求;
- 分数设计有高有低,模拟真实学习情况;
- 平均分略低于用户得分,给予正向反馈。
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('科目对比', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 16.h),
],
),
科目对比标题栏:
- 标题样式与前序模块一致,视觉统一;
- 16h间距,分隔标题与内容区域;
- 左对齐布局,符合阅读习惯。
...subjects.map((subject) {
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: Row(
children: [
SizedBox(
width: 80.w,
child: Text(subject['name'] as String, style: TextStyle(fontSize: 14.sp)),
),
],
),
);
}).toList(),
科目名称列配置:
- 固定80w宽度,保证科目名称展示完整;
- 14sp字号,保证可读性;
- 16h底部间距,分隔不同科目;
- 遍历生成科目行,避免重复代码。
Expanded(
child: Stack(
children: [
Container(
height: 20.h,
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(10.r),
),
),
],
),
),
进度条底层设计:
- 灰色底层作为100分基准线;
- 20h高度+10r圆角,符合进度条设计规范;
- Expanded填充剩余宽度,适配不同屏幕。
Container(
height: 20.h,
width: (subject['avg'] as double) / 100 * MediaQuery.of(context).size.width * 0.5,
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(10.r),
),
),
平均分进度条:
- 宽度按平均分占比计算,最大为屏幕宽度的50%;
- 中灰色标识,区分于用户分数;
- 与底层进度条尺寸一致,保证对齐。
Container(
height: 20.h,
width: (subject['score'] as double) / 100 * MediaQuery.of(context).size.width * 0.5,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(10.r),
),
),
用户分数进度条:
- 主题蓝标识,突出用户数据;
- 宽度计算逻辑与平均分一致,保证对比公平;
- 堆叠在平均分之上,直观展示分数差距。
SizedBox(width: 8.w),
Text(
'${subject['score']}分',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: Colors.blue,
),
),
分数展示样式:
- 8w间距,分隔进度条与分数;
- 加粗+主题蓝,突出用户得分;
- 14sp字号,与科目名称一致,视觉平衡。
SizedBox(height: 12.h),
Row(
children: [
Container(
width: 20.w,
height: 12.h,
decoration: BoxDecoration(
color: Colors.grey[400],
borderRadius: BorderRadius.circular(2.r),
),
),
SizedBox(width: 8.w),
Text('平均分', style: TextStyle(fontSize: 12.sp, color: Colors.grey[600])),
],
),
图例设计(平均分):
- 20w×12h色块,模拟进度条样式;
- 8w间距,分隔色块与文字;
- 12sp浅灰色文字,符合图例设计规范。
SizedBox(width: 24.w),
Container(
width: 20.w,
height: 12.h,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(2.r),
),
),
SizedBox(width: 8.w),
Text('我的分数', style: TextStyle(fontSize: 12.sp, color: Colors.grey[600])),
图例设计(用户分数):
- 24w间距,分隔两个图例;
- 色块颜色与进度条一致,视觉统一;
- 文字说明简洁,符合用户认知。
成绩排名分析
成绩排名分析满足用户的竞争对比需求,设计目标:
- 展示班级/年级排名,定位用户水平;
- 突出用户自身排名,强化视觉焦点;
- 颜色区分排名层级,提升可读性。
Widget _buildScoreRanking() {
final rankings = [
{'rank': 1, 'name': '张同学', 'score': 95, 'isMe': false},
{'rank': 2, 'name': '李同学', 'score': 93, 'isMe': false},
];
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.1), blurRadius: 10, offset: const Offset(0, 4))],
),
);
}
排名数据初始化:
- 包含前两名同学数据,作为对比参考;
- isMe字段标记是否为当前用户,便于样式区分;
- 容器样式复用前序模块,保证页面统一。
rankings.add({'rank': 3, 'name': '我', 'score': 92, 'isMe': true});
rankings.add({'rank': 4, 'name': '王同学', 'score': 90, 'isMe': false});
rankings.add({'rank': 5, 'name': '赵同学', 'score': 88, 'isMe': false});
排名数据补充:
- 用户排名第3,处于中上水平,给予正向反馈;
- 分数梯度合理(95→93→92→90→88),模拟真实排名;
- 覆盖前5名,满足用户核心对比需求。
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('成绩排名', style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.bold)),
SizedBox(height: 16.h),
],
),
排名模块标题栏:
- 标题样式与其他模块一致,视觉统一;
- 16h间距,分隔标题与排名列表;
- 左对齐布局,符合阅读习惯。
...rankings.map((ranking) {
final isMe = ranking['isMe'] as bool;
return Container(
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: isMe ? Colors.blue[50] : Colors.transparent,
borderRadius: BorderRadius.circular(8.r),
),
);
}).toList(),
排名项容器设计:
- 8h底部间距,分隔不同排名项;
- 用户项添加浅蓝色背景,突出焦点;
- 8r圆角,提升卡片质感。
border: isMe ? Border.all(color: Colors.blue) : null,
child: Row(
children: [
CircleAvatar(
radius: 16.r,
backgroundColor: _getRankColor(ranking['rank'] as int),
child: Text('${ranking['rank']}', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 12.sp)),
),
],
),
排名标识设计:
- 用户项添加蓝色边框,强化焦点;
- 圆形头像展示排名数字,视觉醒目;
- 16r半径,保证排名数字清晰展示;
- 白色文字+加粗,提升可读性。
SizedBox(width: 12.w),
Expanded(
child: Text(
ranking['name'] as String,
style: TextStyle(
fontSize: 14.sp,
fontWeight: isMe ? FontWeight.bold : FontWeight.normal,
color: isMe ? Colors.blue : Colors.black,
),
),
),
姓名展示样式:
- 12w间距,分隔排名标识与姓名;
- 用户姓名加粗+主题蓝,突出焦点;
- Expanded保证姓名自适应宽度。
Text(
'${ranking['score']}分',
style: TextStyle(
fontSize: 14.sp,
fontWeight: FontWeight.bold,
color: isMe ? Colors.blue : Colors.black,
),
),
分数展示样式:
- 加粗字体,突出分数核心信息;
- 用户分数用主题蓝,与姓名样式统一;
- 14sp字号,与姓名一致,视觉平衡。
排名颜色映射
排名颜色映射是提升视觉体验的关键,设计逻辑:
- 前三名使用特殊颜色,符合用户对排名的认知;
- 后续排名使用主题蓝,保证视觉统一;
- 颜色选择贴合传统排名标识(金牌/银牌/铜牌)。
Color _getRankColor(int rank) {
switch (rank) {
case 1:
return Colors.amber;
case 2:
return Colors.grey[400]!;
case 3:
return Colors.brown[300]!;
default:
return Colors.blue;
}
}
排名颜色配置说明:
- 第1名:琥珀色(模拟金牌),视觉最醒目;
- 第2名:浅灰色(模拟银牌),次一级视觉;
- 第3名:棕褐色(模拟铜牌),第三级视觉;
- 其他:主题蓝,保证视觉统一。
时间维度切换
时间维度切换功能允许用户查看不同时间段的成绩数据。我们使用PopupMenuButton提供时间选择选项,支持本周、本月、三个月和全部时间。
数据可视化优化
为了提升数据可视化的效果,我们在图表中添加了渐变填充、动画效果和交互提示。同时,使用不同颜色区分不同类型的数据,增强视觉识别度。
响应式设计
成绩分析页面采用响应式设计,能够适配不同屏幕尺寸。我们使用flutter_screenutil插件确保在不同设备上都有良好的显示效果。
数据导出
成绩分析页面可以提供数据导出功能,允许用户将成绩报告导出为PDF或分享给他人。这有助于用户进行学习成果展示。
通过以上实现,我们创建了一个功能完善、视觉丰富的成绩分析页面。这个页面不仅能够帮助用户深入了解自己的成绩表现,还提供了直观的数据可视化和详细的分析功能,为用户的学习提供了有力的数据支持。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)