Flutter for OpenHarmony 视力保护提醒App实战 - table_calendar日历组件
Flutter日历组件实现摘要 本文介绍了在Flutter应用中使用table_calendar库实现日历功能的方法。主要包含以下内容: 核心功能:日历显示、日期选择、事件标记和自定义样式 依赖配置:通过pubspec.yaml添加table_calendar和flutter_screenutil依赖 基础实现: 使用StatefulWidget管理日历状态 初始化当前日期和选中日期 构建包含Ap

概述
table_calendar是Flutter中最流行的日历组件库,它提供了功能完整的日历显示和日期选择功能。在视力保护提醒应用中,我们使用table_calendar来展示月度统计数据和日期选择。本文将详细讲解如何使用table_calendar创建日历组件,包括日期选择、事件标记、自定义样式等功能。
table_calendar的核心功能
table_calendar主要提供以下功能:
- 日历显示 - 展示完整的月度日历
- 日期选择 - 支持单个或多个日期选择
- 事件标记 - 在日期上标记事件
- 自定义样式 - 支持丰富的样式自定义选项
这些功能结合在一起,为应用提供了一个完整的日历解决方案。
项目依赖配置
在pubspec.yaml中,我们已经配置了所需的依赖:
dependencies:
flutter:
sdk: flutter
table_calendar: ^3.0.9
flutter_screenutil: ^5.9.0
table_calendar库提供了日历功能,flutter_screenutil用于屏幕适配。这些依赖都是为了支持鸿蒙系统的Flutter开发。
基础日历实现
基础日历实现包括日期选择和月份导航。首先我们需要创建一个StatefulWidget来管理日历的状态,包括当前焦点日期和选中的日期。通过在initState中初始化这些变量,我们可以确保日历在首次加载时显示当前日期。这种状态管理方式使得日历能够响应用户的交互操作。
class CalendarPage extends StatefulWidget {
const CalendarPage({super.key});
State<CalendarPage> createState() => _CalendarPageState();
}
class _CalendarPageState extends State<CalendarPage> {
late DateTime _focusedDay;
late DateTime _selectedDay;
void initState() {
super.initState();
_focusedDay = DateTime.now();
_selectedDay = DateTime.now();
}
这段代码定义了CalendarPage这个StatefulWidget类。通过继承StatefulWidget,我们可以创建一个有状态的组件,能够在用户交互时更新UI。_focusedDay表示当前显示的月份,_selectedDay表示用户选中的日期。在initState方法中,我们将这两个变量都初始化为当前日期,这样日历首次加载时就会显示今天的日期。late关键字表示这些变量会在后续被初始化,不需要在声明时赋值。
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('日历'),
backgroundColor: const Color(0xFF2196F3),
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
child: Column(
children: [
_buildCalendar(),
SizedBox(height: 16.h),
_buildSelectedDateInfo(),
SizedBox(height: 20.h),
],
),
),
);
}
build方法是Widget的核心方法,返回该Widget的UI结构。Scaffold提供了Material Design的基础框架,包括AppBar和body。AppBar设置了蓝色背景和白色文字,提供了顶部导航栏。SingleChildScrollView使页面内容可以滚动,这在日历下方有其他内容时非常有用。Column布局用于垂直排列日历组件和日期信息显示区域。通过调用_buildCalendar()和_buildSelectedDateInfo()方法,我们将日历和日期信息分离为独立的组件,提高了代码的可读性和复用性。
Widget _buildCalendar() {
return Container(
margin: EdgeInsets.all(16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: TableCalendar(
_buildCalendar方法构建了日历的容器和样式。Container提供了白色背景、圆角和阴影效果,使日历看起来像一个独立的卡片。margin设置外边距,padding设置内边距,这样日历与屏幕边缘和内部内容都有适当的间距。BoxDecoration定义了容器的装饰效果,包括白色背景、12个单位的圆角和阴影。阴影使用黑色半透明颜色,blurRadius为8使阴影看起来柔和。TableCalendar是table_calendar库的核心组件,它提供了完整的日历功能。
firstDay: DateTime.utc(2024, 1, 1),
lastDay: DateTime.utc(2025, 12, 31),
focusedDay: _focusedDay,
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
onPageChanged: (focusedDay) {
_focusedDay = focusedDay;
},
TableCalendar的参数配置了日历的基本功能。firstDay和lastDay定义了日历的可选日期范围,这里设置为2024年1月1日到2025年12月31日。focusedDay表示当前显示的月份,通过_focusedDay变量控制。selectedDayPredicate是一个回调函数,用于判断某个日期是否被选中,这里使用isSameDay方法比较日期。onDaySelected回调在用户点击日期时触发,通过setState更新_selectedDay和_focusedDay,这会触发Widget的重新构建。onPageChanged回调在用户切换月份时触发,更新_focusedDay以显示新的月份。
calendarStyle: CalendarStyle(
selectedDecoration: BoxDecoration(
color: const Color(0xFF2196F3),
shape: BoxShape.circle,
),
todayDecoration: BoxDecoration(
color: const Color(0xFF2196F3).withOpacity(0.3),
shape: BoxShape.circle,
),
weekendTextStyle: TextStyle(
fontSize: 14.sp,
color: Colors.red,
),
defaultTextStyle: TextStyle(fontSize: 14.sp),
),
calendarStyle定义了日期单元格的样式。selectedDecoration设置选中日期的背景为蓝色圆形,这样用户可以清楚地看到选中的日期。todayDecoration设置今天的背景为半透明蓝色,使其与选中日期区分开来。weekendTextStyle将周末文字设置为红色,这是日历的常见做法,可以帮助用户快速识别周末。defaultTextStyle设置普通日期的文字样式,使用14个单位的字体大小。这些样式配置共同构成了日历的视觉效果。
headerStyle: HeaderStyle(
formatButtonVisible: false,
titleCentered: true,
titleTextStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
leftChevronIcon: Icon(Icons.chevron_left, size: 20.sp),
rightChevronIcon: Icon(Icons.chevron_right, size: 20.sp),
),
daysOfWeekStyle: DaysOfWeekStyle(
weekdayStyle: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
weekendStyle: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold, color: Colors.red),
),
),
);
}
headerStyle定义了日历头部的样式。formatButtonVisible设置为false隐藏格式按钮,使头部看起来更简洁。titleCentered设置为true使月份标题居中显示。titleTextStyle定义了月份标题的文字样式,使用16个单位的粗体字体。leftChevronIcon和rightChevronIcon定义了左右导航按钮的图标,使用chevron_left和chevron_right图标。daysOfWeekStyle定义了周标题行的样式,weekdayStyle设置工作日的样式,weekendStyle设置周末的样式并使用红色文字。这些样式配置使日历的导航和周标题更加清晰易读。
Widget _buildSelectedDateInfo() {
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'选中日期',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 12.h),
Text(
'${_selectedDay.year}年${_selectedDay.month}月${_selectedDay.day}日',
style: TextStyle(fontSize: 14.sp, color: const Color(0xFF2196F3)),
),
SizedBox(height: 8.h),
Text(
'星期${_getWeekday(_selectedDay.weekday)}',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
),
],
),
);
}
String _getWeekday(int weekday) {
const weekdays = ['一', '二', '三', '四', '五', '六', '日'];
return weekdays[weekday - 1];
}
}
_buildSelectedDateInfo方法显示用户选中的日期信息。Container提供了与日历相同的样式,包括白色背景、圆角和阴影,使两个卡片看起来一致。Column布局用于垂直排列标题和日期信息。第一个Text显示"选中日期"标题,使用粗体。第二个Text显示完整的日期,格式为"年月日",使用蓝色文字。第三个Text显示星期几,使用灰色文字。_getWeekday方法将weekday数字(1-7)转换为中文星期表示(一-日),这样用户可以看到更易读的星期信息。
事件标记
在日历上标记事件。事件标记功能允许我们在日历上显示特定日期的事件,这对于提醒应用非常重要。我们使用一个Map数据结构来存储每个日期对应的事件列表,这样可以轻松地查询和管理事件数据。通过eventLoader参数,table_calendar能够自动加载并显示这些事件。
class EventCalendarPage extends StatefulWidget {
const EventCalendarPage({super.key});
State<EventCalendarPage> createState() => _EventCalendarPageState();
}
class _EventCalendarPageState extends State<EventCalendarPage> {
late DateTime _focusedDay;
late DateTime _selectedDay;
final Map<DateTime, List<String>> _events = {
DateTime(2024, 1, 15): ['提醒1', '提醒2'],
DateTime(2024, 1, 20): ['提醒3'],
DateTime(2024, 1, 25): ['提醒4', '提醒5', '提醒6'],
};
void initState() {
super.initState();
_focusedDay = DateTime.now();
_selectedDay = DateTime.now();
}
这段代码定义了EventCalendarPage这个StatefulWidget类。通过继承StatefulWidget,我们可以创建一个有状态的组件。_events是一个Map,键是DateTime对象,值是该日期的事件列表。这种数据结构使得我们可以快速查找某个日期的所有事件。在实际应用中,这个Map可以从数据库或API获取,以支持动态的事件数据。这里我们使用硬编码的数据作为示例,展示了如何在特定日期存储多个事件。
List<String> _getEventsForDay(DateTime day) {
return _events[day] ?? [];
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('事件日历'),
backgroundColor: const Color(0xFF2196F3),
foregroundColor: Colors.white,
),
body: SingleChildScrollView(
child: Column(
children: [
_buildEventCalendar(),
SizedBox(height: 16.h),
_buildEventList(),
SizedBox(height: 20.h),
],
),
),
);
}
_getEventsForDay方法根据日期查询事件列表。这个方法使用Map的[]操作符查找指定日期的事件,如果该日期没有事件则返回空列表(??操作符)。build方法的结构与基础日历类似,使用Scaffold和Column布局。通过调用_buildEventCalendar()和_buildEventList()方法,我们将事件日历和事件列表分离为独立的组件。这种设计使代码更加模块化和易于维护。
Widget _buildEventCalendar() {
return Container(
margin: EdgeInsets.all(16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: TableCalendar(
firstDay: DateTime.utc(2024, 1, 1),
lastDay: DateTime.utc(2025, 12, 31),
focusedDay: _focusedDay,
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
eventLoader: _getEventsForDay,
_buildEventCalendar方法构建了事件日历的容器。Container提供了与基础日历相同的样式。TableCalendar的基本参数配置与基础日历相同,包括firstDay、lastDay、focusedDay等。关键的区别是eventLoader参数,它将_getEventsForDay方法传递给TableCalendar,使其能够自动加载并显示事件标记。当用户点击日期时,onDaySelected回调会更新_selectedDay和_focusedDay,这会触发_buildEventList方法重新构建以显示新选中日期的事件。
calendarStyle: CalendarStyle(
selectedDecoration: BoxDecoration(
color: const Color(0xFF2196F3),
shape: BoxShape.circle,
),
todayDecoration: BoxDecoration(
color: const Color(0xFF2196F3).withOpacity(0.3),
shape: BoxShape.circle,
),
markerDecoration: BoxDecoration(
color: const Color(0xFFFFC107),
shape: BoxShape.circle,
),
),
headerStyle: HeaderStyle(
formatButtonVisible: false,
titleCentered: true,
titleTextStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
),
);
}
calendarStyle定义了日期单元格的样式。selectedDecoration和todayDecoration与基础日历相同。markerDecoration定义了事件标记的样式,这里使用黄色圆形标记。当某个日期有事件时,table_calendar会在该日期下方显示这个黄色标记,使用户能够快速识别有事件的日期。headerStyle定义了日历头部的样式,与基础日历相同。
Widget _buildEventList() {
final events = _getEventsForDay(_selectedDay);
return Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.all(16.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'该日期的事件',
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
SizedBox(height: 12.h),
if (events.isEmpty)
Text(
'暂无事件',
style: TextStyle(fontSize: 12.sp, color: Colors.grey),
)
else
...events.map((event) => Padding(
padding: EdgeInsets.symmetric(vertical: 4.h),
child: Row(
children: [
Icon(Icons.event, size: 16.sp, color: const Color(0xFF2196F3)),
SizedBox(width: 8.w),
Text(
event,
style: TextStyle(fontSize: 12.sp),
),
],
),
)),
],
),
);
}
}
_buildEventList方法显示选中日期的所有事件。首先调用_getEventsForDay(_selectedDay)获取选中日期的事件列表。Container提供了与日历相同的样式。Column布局用于垂直排列标题和事件列表。使用if-else判断是否有事件,如果没有事件则显示"暂无事件"提示。如果有事件,使用…events.map()语法将事件列表转换为多个Row组件,每个Row显示一个事件。每个Row包含一个事件图标和事件文字,使用Padding添加垂直间距。这种设计使事件列表清晰易读。
自定义样式
table_calendar支持丰富的样式自定义。通过CalendarStyle、HeaderStyle和DaysOfWeekStyle等配置对象,我们可以精细地控制日历的外观。这些样式配置允许我们改变日期单元格的颜色、形状、文字样式等,以及头部导航栏和周标题行的样式。通过合理的样式配置,我们可以创建符合应用设计风格的日历组件。
calendarStyle: CalendarStyle(
selectedDecoration: BoxDecoration(
color: const Color(0xFF2196F3),
shape: BoxShape.circle,
),
todayDecoration: BoxDecoration(
color: const Color(0xFF2196F3).withOpacity(0.3),
shape: BoxShape.circle,
),
weekendTextStyle: TextStyle(
fontSize: 14.sp,
color: Colors.red,
),
defaultTextStyle: TextStyle(fontSize: 14.sp),
outsideTextStyle: TextStyle(
fontSize: 14.sp,
color: Colors.grey[300],
),
),
calendarStyle定义了日期单元格的样式。selectedDecoration设置选中日期的背景为蓝色圆形,todayDecoration设置今天的背景为半透明蓝色。weekendTextStyle将周末文字设置为红色,defaultTextStyle设置普通日期的文字样式。outsideTextStyle设置当月之外日期的文字样式,通常使用灰色使其看起来不可选。这些样式配置共同构成了日历的视觉效果。
headerStyle: HeaderStyle(
formatButtonVisible: false,
titleCentered: true,
titleTextStyle: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
leftChevronIcon: Icon(Icons.chevron_left, size: 20.sp),
rightChevronIcon: Icon(Icons.chevron_right, size: 20.sp),
),
daysOfWeekStyle: DaysOfWeekStyle(
weekdayStyle: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold),
weekendStyle: TextStyle(fontSize: 12.sp, fontWeight: FontWeight.bold, color: Colors.red),
),
headerStyle定义了日历头部的样式。formatButtonVisible设置为false隐藏格式按钮,titleCentered设置为true使月份标题居中显示。titleTextStyle定义了月份标题的文字样式。leftChevronIcon和rightChevronIcon定义了左右导航按钮的图标。daysOfWeekStyle定义了周标题行的样式,weekdayStyle设置工作日的样式,weekendStyle设置周末的样式并使用红色文字。这些样式配置使日历的导航和周标题更加清晰易读。
屏幕适配处理
在整个日历中,我们使用flutter_screenutil库来处理屏幕适配。.w表示宽度单位,.h表示高度单位,.sp表示字体大小单位。这样可以确保在不同屏幕尺寸的设备上,日历都能正确显示。
例如,SizedBox(height: 16.h)表示高度为16个高度单位。TextStyle(fontSize: 16.sp)表示字体大小为16个字体单位。
总结
table_calendar是一个功能完整的日历组件库,它提供了日期选择、事件标记、自定义样式等功能。通过使用table_calendar,我们可以轻松地创建功能完整的日历组件。通过flutter_screenutil库,我们确保了日历在不同屏幕尺寸上的正确显示。
在视力保护提醒应用中,我们使用table_calendar来展示月度统计数据和日期选择,帮助用户更好地管理和查看提醒数据。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)