在这里插入图片描述

在完成了项目的初始化和架构设计后,我们开始实现应用的核心功能。学习主页是用户打开应用后看到的第一个界面,它承载着展示学习数据、提供快捷入口、推荐学习内容等重要功能。一个设计良好的主页可以让用户快速了解自己的学习状态,方便地访问各项功能。

主页的设计理念

学习主页的设计遵循了信息层次清晰、操作便捷的原则。页面从上到下分为四个主要区域:欢迎卡片、快捷功能、今日进度和推荐学习。每个区域都有明确的功能定位,用户可以一目了然地获取信息。

欢迎卡片位于页面顶部,使用渐变色背景营造温暖的氛围。它不仅显示问候语,还展示了今日的学习数据摘要。这种设计让用户在打开应用的第一时间就能看到自己的学习成果,产生成就感。

快捷功能区域采用网格布局,将七个核心功能以图标的形式展示。每个功能都有独特的颜色标识,方便用户快速识别和点击。这种设计比传统的列表更加直观,也更节省空间。

今日进度区域展示了各科目的学习进度。使用进度条的形式可以直观地看出完成情况,不同的颜色区分不同的科目。这种可视化的展示方式比单纯的数字更有吸引力。

推荐学习区域根据用户的学习历史和偏好,推荐合适的学习内容。这种个性化的推荐可以帮助用户发现感兴趣的内容,提高学习效率。

页面结构的实现

学习主页使用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

Logo

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

更多推荐