flutter_for_openharmony智慧学习助手app实战:学习主页实现
本文介绍了学习助手应用主页的设计与实现。主页采用层次清晰的设计,包含欢迎卡片、快捷功能、今日进度和推荐学习四大区域。欢迎卡片使用渐变色背景展示问候语和学习数据;快捷功能区域采用网格布局提供七个核心功能入口;今日进度区域通过进度条直观展示学习情况;推荐学习区域提供个性化内容推荐。实现上使用StatelessWidget构建,采用响应式布局确保不同屏幕尺寸的适配性。这种设计既保证了功能完整性,又提升了

在完成了项目的初始化和架构设计后,我们开始实现应用的核心功能。学习主页是用户打开应用后看到的第一个界面,它承载着展示学习数据、提供快捷入口、推荐学习内容等重要功能。一个设计良好的主页可以让用户快速了解自己的学习状态,方便地访问各项功能。
主页的设计理念
学习主页的设计遵循了信息层次清晰、操作便捷的原则。页面从上到下分为四个主要区域:欢迎卡片、快捷功能、今日进度和推荐学习。每个区域都有明确的功能定位,用户可以一目了然地获取信息。
欢迎卡片位于页面顶部,使用渐变色背景营造温暖的氛围。它不仅显示问候语,还展示了今日的学习数据摘要。这种设计让用户在打开应用的第一时间就能看到自己的学习成果,产生成就感。
快捷功能区域采用网格布局,将七个核心功能以图标的形式展示。每个功能都有独特的颜色标识,方便用户快速识别和点击。这种设计比传统的列表更加直观,也更节省空间。
今日进度区域展示了各科目的学习进度。使用进度条的形式可以直观地看出完成情况,不同的颜色区分不同的科目。这种可视化的展示方式比单纯的数字更有吸引力。
推荐学习区域根据用户的学习历史和偏好,推荐合适的学习内容。这种个性化的推荐可以帮助用户发现感兴趣的内容,提高学习效率。
页面结构的实现
学习主页使用StatelessWidget实现,因为它不需要维护复杂的状态。页面的数据可以从外部传入,或者通过GetX等状态管理工具获取。
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'daily_plan_page.dart';
import 'study_timer_page.dart';
import 'focus_mode_page.dart';
import 'learning_stats_page.dart';
import 'study_calendar_page.dart';
import 'quick_notes_page.dart';
import 'flashcards_page.dart';
class LearningHomePage extends StatelessWidget {
const LearningHomePage({super.key});
导入语句引入了所有需要的依赖。flutter_screenutil用于屏幕适配,get用于页面导航。我们还导入了七个子页面,对应快捷功能区域的七个入口。
使用StatelessWidget而不是StatefulWidget是因为主页本身不需要维护状态。虽然页面上显示的数据会变化,但这些数据来自外部,不是页面内部的状态。这种设计让组件更加纯粹,也更容易测试。
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
title: const Text('智慧学习助手'),
elevation: 0,
actions: [
IconButton(
icon: const Icon(Icons.notifications_outlined),
onPressed: () {},
),
],
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildWelcomeCard(),
SizedBox(height: 20.h),
_buildQuickActions(),
SizedBox(height: 20.h),
_buildTodayProgress(),
SizedBox(height: 20.h),
_buildRecommendations(),
],
),
),
);
}
Scaffold提供了标准的页面结构。我们设置背景色为浅灰色,这比纯白色更柔和,长时间使用不会让眼睛疲劳。AppBar的elevation设置为0,去掉了阴影,让顶部区域与页面融为一体。
AppBar右侧的通知图标使用了outlined样式,这是Material 3推荐的图标样式。虽然目前点击没有实现功能,但预留了这个入口,将来可以添加通知功能。
body使用SingleChildScrollView包裹,让页面可以滚动。当内容超过屏幕高度时,用户可以向下滚动查看更多内容。Column组件将各个区域垂直排列,crossAxisAlignment设置为start让内容左对齐。
各个区域之间使用SizedBox添加间距。20.h表示20个单位的高度,会根据屏幕尺寸自动缩放。这种统一的间距让页面看起来更加整齐。
欢迎卡片的实现
欢迎卡片是页面的视觉焦点,使用渐变色背景和白色文字营造温暖的氛围。
Widget _buildWelcomeCard() {
return Container(
margin: EdgeInsets.all(16.w),
padding: EdgeInsets.all(20.w),
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.blue, Colors.lightBlueAccent],
),
borderRadius: BorderRadius.circular(16.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'早上好!',
style: TextStyle(
color: Colors.white,
fontSize: 24.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 8.h),
Text(
'今天也要加油学习哦!',
style: TextStyle(
color: Colors.white70,
fontSize: 14.sp,
),
),
SizedBox(height: 16.h),
Row(
children: [
_buildStatItem('今日学习', '2.5小时'),
SizedBox(width: 20.w),
_buildStatItem('完成任务', '5/8'),
],
),
],
),
);
}
Container的margin设置外边距,padding设置内边距。decoration使用BoxDecoration来定义视觉样式。LinearGradient创建了从蓝色到浅蓝色的渐变效果,这种渐变比单一颜色更有层次感。
borderRadius设置圆角,16.r表示16个单位的圆角半径。圆角让卡片看起来更加柔和,符合现代UI设计的趋势。
问候语使用大号粗体白色文字,非常醒目。鼓励语使用较小的半透明白色文字,形成层次对比。这种大小和透明度的变化让文字更有层次感。
底部的统计数据使用Row横向排列。两个数据项之间有20个单位的间距,让它们不会挤在一起。这些数据目前是硬编码的,实际应用中应该从数据源获取。
Widget _buildStatItem(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: TextStyle(
color: Colors.white70,
fontSize: 12.sp,
),
),
Text(
value,
style: TextStyle(
color: Colors.white,
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
],
);
}
统计项使用Column垂直排列标签和数值。标签使用小号半透明文字,数值使用大号粗体文字。这种设计让数值更加突出,用户可以一眼看到关键信息。
crossAxisAlignment设置为start让文字左对齐。如果设置为center,文字会居中对齐,但那样不太符合阅读习惯。
快捷功能区域的实现
快捷功能区域是主页的核心,提供了七个常用功能的快速入口。
Widget _buildQuickActions() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'快捷功能',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12.h),
GridView.count(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
crossAxisCount: 4,
mainAxisSpacing: 12.h,
crossAxisSpacing: 12.w,
children: [
_buildActionItem(Icons.calendar_today, '每日计划', Colors.orange, () {
Get.to(() => const DailyPlanPage());
}),
_buildActionItem(Icons.timer, '学习计时', Colors.green, () {
Get.to(() => const StudyTimerPage());
}),
_buildActionItem(Icons.lightbulb, '专注模式', Colors.purple, () {
Get.to(() => const FocusMoPage());
}),
_buildActionItem(Icons.bar_chart, '学习统计', Colors.blue, () {
Get.to(() => const LearningStatsPage());
}),
_buildActionItem(Icons.event_note, '学习日历', Colors.red, () {
Get.to(() => const StudyCalendarPage());
}),
_buildActionItem(Icons.note_add, '快速笔记', Colors.teal, () {
Get.to(() => const QuickNotesPage());
}),
_buildActionItem(Icons.style, '记忆卡片', Colors.pink, () {
Get.to(() => const FlashcardsPage());
}),
],
),
],
),
);
}
GridView.count创建了一个网格布局,crossAxisCount设置为4表示每行显示4个项目。shrinkWrap设置为true让GridView只占用需要的高度,而不是填满整个父容器。
physics设置为NeverScrollableScrollPhysics禁用了GridView自己的滚动。因为外层已经有SingleChildScrollView,如果GridView也可以滚动,会产生滚动冲突。
mainAxisSpacing和crossAxisSpacing设置了网格项之间的间距。这些间距让布局看起来不那么拥挤,也更容易点击。
每个功能项都通过_buildActionItem方法创建,传入图标、标签、颜色和点击回调。使用不同的颜色可以让用户快速识别不同的功能。
点击回调使用Get.to进行页面跳转。这是GetX提供的路由方法,比传统的Navigator.push更简洁。我们不需要传递BuildContext,代码更加清晰。
Widget _buildActionItem(IconData icon, String label, Color color, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 50.w,
height: 50.w,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(icon, color: color, size: 28.sp),
),
SizedBox(height: 8.h),
Text(
label,
style: TextStyle(fontSize: 12.sp),
textAlign: TextAlign.center,
),
],
),
);
}
功能项使用GestureDetector包裹,让整个区域都可以点击。相比于只让图标可点击,这种方式提供了更大的点击区域,用户操作更方便。
Column将图标和文字垂直排列,mainAxisAlignment设置为center让内容居中显示。图标容器使用半透明的背景色,这种设计比纯色背景更柔和,也更有层次感。
图标大小设置为28.sp,这个尺寸在手机屏幕上显示效果很好。太小的图标不容易识别,太大的图标会显得笨重。
文字使用textAlign设置居中对齐。因为有些标签是四个字,有些是三个字,居中对齐可以让它们看起来更整齐。
今日进度区域的实现
今日进度区域展示了各科目的学习完成情况,使用进度条的形式直观展示。
Widget _buildTodayProgress() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'今日进度',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12.h),
_buildProgressItem('数学', 0.8, Colors.blue),
SizedBox(height: 8.h),
_buildProgressItem('英语', 0.6, Colors.green),
SizedBox(height: 8.h),
_buildProgressItem('物理', 0.4, Colors.orange),
],
),
);
}
这个区域使用白色背景的卡片,与页面的浅灰色背景形成对比。卡片设计是现代UI中常用的模式,它可以将相关的内容组织在一起,视觉上更加清晰。
标题使用粗体文字,让用户知道这个区域的功能。下方列出了三个科目的进度,每个科目之间有8个单位的间距。
这里展示的科目是硬编码的,实际应用中应该根据用户的学习计划动态生成。不同用户可能学习不同的科目,进度数据也应该从数据库读取。
Widget _buildProgressItem(String subject, double progress, Color color) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(subject, style: TextStyle(fontSize: 14.sp)),
Text('${(progress * 100).toInt()}%', style: TextStyle(fontSize: 12.sp)),
],
),
SizedBox(height: 4.h),
LinearProgressIndicator(
value: progress,
backgroundColor: color.withOpacity(0.2),
valueColor: AlwaysStoppedAnimation<Color>(color),
),
],
);
}
进度项的顶部使用Row将科目名称和百分比左右排列。mainAxisAlignment设置为spaceBetween让它们分别靠左和靠右,中间自动填充空白。
百分比通过计算得出,将0到1的进度值转换为0到100的百分比。toInt()方法去掉小数部分,让显示更简洁。
LinearProgressIndicator是Flutter提供的进度条组件。value参数设置进度值,范围是0到1。backgroundColor设置背景色,使用半透明的主色调。valueColor设置进度条的颜色,使用AlwaysStoppedAnimation包裹让颜色保持不变。
不同科目使用不同的颜色,这种设计让用户可以快速识别。颜色的选择也有讲究,蓝色代表理性的数学,绿色代表语言类的英语,橙色代表实验性的物理。
推荐学习区域的实现
推荐学习区域展示了系统推荐的学习内容,帮助用户发现感兴趣的课程。
Widget _buildRecommendations() {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'推荐学习',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 12.h),
_buildRecommendationCard(
'高等数学',
'微积分基础',
'45分钟',
Colors.blue,
),
SizedBox(height: 12.h),
_buildRecommendationCard(
'英语四级',
'词汇强化训练',
'30分钟',
Colors.green,
),
],
),
);
}
推荐区域的结构与进度区域类似,都是标题加内容列表。这里展示了两个推荐课程,每个课程之间有12个单位的间距。
推荐算法是学习应用的核心功能之一。虽然这里的推荐是硬编码的,但实际应用中应该根据用户的学习历史、兴趣偏好、学习目标等因素进行智能推荐。
Widget _buildRecommendationCard(String title, String subtitle, String duration, Color color) {
return Container(
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
children: [
Container(
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(Icons.book, color: color, size: 30.sp),
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 4.h),
Text(
subtitle,
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
),
),
SizedBox(height: 4.h),
Row(
children: [
Icon(Icons.access_time, size: 14.sp, color: Colors.grey),
SizedBox(width: 4.w),
Text(
duration,
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
],
),
],
),
),
Icon(Icons.arrow_forward_ios, size: 16.sp, color: Colors.grey),
],
),
);
}
推荐卡片使用横向布局,左侧是课程图标,中间是课程信息,右侧是箭头图标。这种布局在移动应用中很常见,用户一看就知道可以点击进入详情。
课程图标使用带背景的Icon,背景色与课程类型对应。虽然这里都使用book图标,但实际应用中可以根据课程类型使用不同的图标。
中间的课程信息使用Expanded包裹,让它占据剩余的所有空间。这样无论课程名称多长,都不会影响布局。
课程信息包含三行:标题、副标题和时长。标题使用粗体大号文字,副标题和时长使用灰色小号文字。这种层次分明的设计让信息更容易阅读。
时长信息使用图标和文字组合,图标让信息更直观。access_time图标是Material Icons中表示时间的标准图标。
右侧的箭头图标提示用户可以点击。虽然整个卡片都可以点击,但箭头图标是一个明确的视觉提示。
页面交互的优化
虽然主页主要是展示信息,但良好的交互设计可以提升用户体验。
点击快捷功能图标时,应该有视觉反馈。GestureDetector默认没有点击效果,我们可以使用InkWell替代,它会在点击时显示水波纹效果。
Widget _buildActionItemWithInkWell(IconData icon, String label, Color color, VoidCallback onTap) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12.r),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 50.w,
height: 50.w,
decoration: BoxDecoration(
color: color.withOpacity(0.1),
borderRadius: BorderRadius.circular(12.r),
),
child: Icon(icon, color: color, size: 28.sp),
),
SizedBox(height: 8.h),
Text(
label,
style: TextStyle(fontSize: 12.sp),
textAlign: TextAlign.center,
),
],
),
);
}
InkWell提供了Material Design风格的点击效果。borderRadius参数让水波纹效果遵循圆角,看起来更自然。这种细节的优化虽然不明显,但可以让应用感觉更精致。
页面滚动时,AppBar可以添加阴影效果,让用户知道页面在滚动。这可以通过监听滚动位置来实现。
class LearningHomePageWithScroll extends StatefulWidget {
const LearningHomePageWithScroll({super.key});
State<LearningHomePageWithScroll> createState() => _LearningHomePageWithScrollState();
}
class _LearningHomePageWithScrollState extends State<LearningHomePageWithScroll> {
final ScrollController _scrollController = ScrollController();
bool _showShadow = false;
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.offset > 0 && !_showShadow) {
setState(() => _showShadow = true);
} else if (_scrollController.offset <= 0 && _showShadow) {
setState(() => _showShadow = false);
}
});
}
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
ScrollController用于监听滚动事件。当滚动位置大于0时,显示阴影;当滚动到顶部时,隐藏阴影。这种动态效果让界面更有层次感。
在dispose方法中释放ScrollController是很重要的。如果不释放,会导致内存泄漏。这是Flutter开发中常见的陷阱,需要特别注意。
数据加载与状态管理
主页显示的数据需要从某个地方获取。在实际应用中,这些数据可能来自本地数据库或远程服务器。
使用GetX的状态管理,我们可以创建一个Controller来管理主页的数据。
class HomeController extends GetxController {
final todayStudyHours = 0.0.obs;
final completedTasks = 0.obs;
final totalTasks = 0.obs;
final subjects = <SubjectProgress>[].obs;
final recommendations = <Course>[].obs;
void onInit() {
super.onInit();
loadData();
}
Future<void> loadData() async {
// 从数据库或API加载数据
todayStudyHours.value = 2.5;
completedTasks.value = 5;
totalTasks.value = 8;
subjects.value = [
SubjectProgress('数学', 0.8, Colors.blue),
SubjectProgress('英语', 0.6, Colors.green),
SubjectProgress('物理', 0.4, Colors.orange),
];
recommendations.value = [
Course('高等数学', '微积分基础', '45分钟', Colors.blue),
Course('英语四级', '词汇强化训练', '30分钟', Colors.green),
];
}
Future<void> refresh() async {
await loadData();
}
}
Controller中的变量使用.obs后缀,这是GetX的响应式变量。当这些变量的值改变时,使用它们的Widget会自动重建。
onInit方法在Controller创建时调用,这是加载初始数据的好地方。loadData方法负责从数据源获取数据,这里使用模拟数据,实际应用中应该调用数据库或API。
refresh方法用于刷新数据,可以在用户下拉刷新时调用。
在页面中使用Controller很简单:
class LearningHomePageWithController extends StatelessWidget {
const LearningHomePageWithController({super.key});
Widget build(BuildContext context) {
final controller = Get.put(HomeController());
return Scaffold(
body: Obx(() => SingleChildScrollView(
child: Column(
children: [
_buildWelcomeCard(
controller.todayStudyHours.value,
controller.completedTasks.value,
controller.totalTasks.value,
),
// 其他组件...
],
),
)),
);
}
}
Get.put注册Controller,Obx包裹需要响应数据变化的Widget。当Controller中的响应式变量改变时,Obx内的Widget会自动重建。
这种状态管理方式比StatefulWidget的setState更灵活,特别是在数据需要跨页面共享时。
下拉刷新功能
用户可能需要刷新主页数据,下拉刷新是移动应用的标准交互。
Widget buildWithRefresh() {
final controller = Get.find<HomeController>();
return RefreshIndicator(
onRefresh: controller.refresh,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: Column(
children: [
// 页面内容...
],
),
),
);
}
RefreshIndicator包裹可滚动的Widget,提供下拉刷新功能。onRefresh回调返回一个Future,刷新完成后Future完成,加载指示器消失。
physics设置为AlwaysScrollableScrollPhysics确保即使内容不足一屏也可以滚动,这样用户才能触发下拉刷新。
性能优化考虑
主页是用户最常访问的页面,性能优化特别重要。
使用const构造函数可以让Flutter复用Widget实例,减少重建开销。在我们的代码中,很多Widget都使用了const构造函数。
const Text('早上好!') // 好
Text('早上好!') // 不好,每次都创建新实例
对于列表项,如果数量很多,应该使用ListView.builder而不是Column。builder方式只构建可见的项,性能更好。
ListView.builder(
itemCount: subjects.length,
itemBuilder: (context, index) {
return _buildProgressItem(
subjects[index].name,
subjects[index].progress,
subjects[index].color,
);
},
)
图片加载也需要优化。如果推荐卡片使用真实的课程封面图,应该使用缓存避免重复下载。
CachedNetworkImage(
imageUrl: course.coverUrl,
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
无障碍支持
为主页添加无障碍支持可以让更多人使用应用。
为图标添加语义标签,屏幕阅读器会读出这些标签。
Semantics(
label: '每日计划',
button: true,
child: _buildActionItem(Icons.calendar_today, '每日计划', Colors.orange, () {}),
)
确保颜色对比度足够高。文字和背景的对比度应该至少为4.5:1,这样视力不好的用户也能清楚地看到内容。
可点击区域应该足够大。Material Design建议最小点击区域为48x48dp,我们的快捷功能图标满足这个要求。
总结
通过这篇文章,我们实现了学习助手的主页。主页包含了欢迎卡片、快捷功能、今日进度和推荐学习四个主要区域,为用户提供了清晰的信息展示和便捷的功能入口。
我们使用了多种Flutter组件和技术,包括Scaffold、SingleChildScrollView、GridView、LinearProgressIndicator等。通过合理的布局和样式设计,创建了一个美观实用的主页。
我们还讨论了状态管理、数据加载、下拉刷新、性能优化等实际开发中的重要话题。这些知识不仅适用于主页,也适用于其他页面的开发。
在下一篇文章中,我们将实现每日计划功能。用户可以添加、编辑、删除学习任务,标记任务完成状态。这个功能涉及到表单处理、数据持久化等新的知识点。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)