You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.13 11:39:04