如何基于Flutter实现支持横向滚动的周历组件
Flutter 横向滚动周历组件实现方案
要实现的横向滚动周历效果参考如下:
下面是具体实现步骤和代码:
核心实现思路
- 支持无限滚动的场景推荐用
PageView实现横向翻页,每周对应一个PageView子项;如果只需要有限范围的周历,用SingleChildScrollView + Row即可 - 封装日期工具类生成每周的7天日期数据,支持自定义周起始日(周一/周日)
- 维护选中日期状态,点击日期卡片时触发状态更新和业务回调
1. 日期工具类封装
提前封装通用的日期处理逻辑,方便后续调用:
class DateUtil { // 获取指定日期所在周的7天,当前以周一为周起始,可自行调整为周日 static List<DateTime> getWeekDays(DateTime date) { int weekday = date.weekday; DateTime startOfWeek = date.subtract(Duration(days: weekday - 1)); List<DateTime> weekDays = []; for (int i = 0; i < 7; i++) { weekDays.add(startOfWeek.add(Duration(days: i))); } return weekDays; } // 格式化输出星期 static String formatWeekday(DateTime date) { const weekdays = ["一", "二", "三", "四", "五", "六", "日"]; return weekdays[date.weekday - 1]; } // 判断两个日期是否为同一天 static bool isSameDay(DateTime a, DateTime b) { return a.year == b.year && a.month == b.month && a.day == b.day; } }
2. 周历组件核心代码
class HorizontalWeekCalendar extends StatefulWidget { // 日期选中回调 final Function(DateTime) onDateSelected; // 初始选中日期,默认选今天 final DateTime? initialSelectedDate; const HorizontalWeekCalendar({super.key, required this.onDateSelected, this.initialSelectedDate}); @override State<HorizontalWeekCalendar> createState() => _HorizontalWeekCalendarState(); } class _HorizontalWeekCalendarState extends State<HorizontalWeekCalendar> { late DateTime selectedDate; // 初始页设为中间值,实现近似无限滚动的效果 final PageController _pageController = PageController(initialPage: 1000); final int _totalPages = 2000; // 前后各支持1000周滚动,足够日常使用 @override void initState() { super.initState(); selectedDate = widget.initialSelectedDate ?? DateTime.now(); } @override Widget build(BuildContext context) { return SizedBox( height: 80, // 组件高度可按需调整 child: PageView.builder( controller: _pageController, itemCount: _totalPages, itemBuilder: (context, index) { // 计算当前页对应的周日期 int weekDiff = index - 1000; DateTime currentWeekBaseDate = DateTime.now().add(Duration(days: weekDiff * 7)); List<DateTime> weekDays = DateUtil.getWeekDays(currentWeekBaseDate); return Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: weekDays.map((date) { bool isSelected = DateUtil.isSameDay(date, selectedDate); bool isToday = DateUtil.isSameDay(date, DateTime.now()); return GestureDetector( onTap: () { setState(() => selectedDate = date); widget.onDateSelected(date); }, child: Container( width: 45, decoration: BoxDecoration( color: isSelected ? Colors.blue : Colors.transparent, borderRadius: BorderRadius.circular(8), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( DateUtil.formatWeekday(date), style: TextStyle( color: isSelected ? Colors.white : (isToday ? Colors.blue : Colors.grey[600]), fontSize: 12, ), ), const SizedBox(height: 4), Text( "${date.day}", style: TextStyle( color: isSelected ? Colors.white : (isToday ? Colors.blue : Colors.black), fontSize: 16, fontWeight: isSelected || isToday ? FontWeight.bold : FontWeight.normal, ), ), ], ), ), ); }).toList(), ); }, ), ); } @override void dispose() { _pageController.dispose(); super.dispose(); } }
3. 组件调用示例
直接在页面中引入组件即可使用:
HorizontalWeekCalendar( initialSelectedDate: DateTime.now(), onDateSelected: (selectedDate) { // 处理日期选中后的业务逻辑 print("选中日期:$selectedDate"); }, )
可选优化点
- 不需要无限滚动可直接将
PageView替换为SingleChildScrollView,设置scrollDirection: Axis.horizontal即可实现普通横向滚动 - 可自行调整周起始日、卡片样式、选中态颜色等参数匹配设计稿
- 需要跳转到指定日期的周时,通过
_pageController.animateToPage方法即可实现,跳转页码根据目标日期和当前日期的周差计算即可
内容的提问来源于stack exchange,提问作者Phanindra
相关产品推荐
相关产品推荐

