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

如何在Flutter中实现可切换周且支持自定义的周历Widget

自定义Flutter周历Widget实现方案

你需要的是一款支持前后周切换、可自定义单元格样式的固定周块布局日历,现有DatePicker组件无法满足需求,以下是完全自定义的实现方案:

核心实现思路

  • 维护当前展示周的起始日期(默认周一为周起始,可按需调整为周日)
  • 根据起始日期生成一周7天的日期列表
  • 自定义日期单元格,支持背景色、圆角、选中状态等样式配置
  • 添加周切换按钮,通过修改起始日期刷新视图

完整代码示例

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

class CustomWeekCalendar extends StatefulWidget {
  final DateTime? initialDate;
  final Color cellBgColor;
  final Color selectedCellBgColor;
  final double cellBorderRadius;
  final ValueChanged<DateTime>? onDateSelected;

  const CustomWeekCalendar({
    super.key,
    this.initialDate,
    this.cellBgColor = Colors.grey[100]!,
    this.selectedCellBgColor = Colors.blue,
    this.cellBorderRadius = 8,
    this.onDateSelected,
  });

  @override
  State<CustomWeekCalendar> createState() => _CustomWeekCalendarState();
}

class _CustomWeekCalendarState extends State<CustomWeekCalendar> {
  late DateTime currentWeekStart;
  DateTime? selectedDate;

  @override
  void initState() {
    super.initState();
    // 初始化当前周起始日期(周一为起始)
    final initDate = widget.initialDate ?? DateTime.now();
    currentWeekStart = _getWeekStart(initDate);
    selectedDate = initDate;
  }

  // 获取指定日期所在周的起始日期(周一)
  DateTime _getWeekStart(DateTime date) {
    final weekday = date.weekday;
    return date.subtract(Duration(days: weekday - 1));
  }

  // 生成当前周的7天日期列表
  List<DateTime> _getCurrentWeekDates() {
    return List.generate(7, (index) => currentWeekStart.add(Duration(days: index)));
  }

  // 切换到上一周
  void _previousWeek() {
    setState(() {
      currentWeekStart = currentWeekStart.subtract(const Duration(days: 7));
    });
  }

  // 切换到下一周
  void _nextWeek() {
    setState(() {
      currentWeekStart = currentWeekStart.add(const Duration(days: 7));
    });
  }

  // 格式化日期显示(如:10/12)
  String _formatDate(DateTime date) {
    return DateFormat('MM/dd').format(date);
  }

  // 格式化星期显示(如:周一)
  String _formatWeekday(DateTime date) {
    return DateFormat('EEE', 'zh_CN').format(date);
  }

  @override
  Widget build(BuildContext context) {
    final weekDates = _getCurrentWeekDates();
    return Column(
      children: [
        // 周切换栏
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            IconButton(
              icon: const Icon(Icons.chevron_left),
              onPressed: _previousWeek,
            ),
            Text(
              '${_formatDate(weekDates.first)} - ${_formatDate(weekDates.last)}',
              style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
            ),
            IconButton(
              icon: const Icon(Icons.chevron_right),
              onPressed: _nextWeek,
            ),
          ],
        ),
        const SizedBox(height: 12),
        // 日期单元格行
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: weekDates.map((date) {
            final isSelected = isSameDate(selectedDate, date);
            return GestureDetector(
              onTap: () {
                setState(() {
                  selectedDate = date;
                });
                widget.onDateSelected?.call(date);
              },
              child: Container(
                width: MediaQuery.of(context).size.width / 8,
                padding: const EdgeInsets.symmetric(vertical: 12),
                decoration: BoxDecoration(
                  color: isSelected ? widget.selectedCellBgColor : widget.cellBgColor,
                  borderRadius: BorderRadius.circular(widget.cellBorderRadius),
                ),
                child: Column(
                  children: [
                    Text(
                      _formatWeekday(date),
                      style: TextStyle(
                        color: isSelected ? Colors.white : Colors.black87,
                        fontSize: 12,
                      ),
                    ),
                    const SizedBox(height: 8),
                    Text(
                      date.day.toString(),
                      style: TextStyle(
                        color: isSelected ? Colors.white : Colors.black,
                        fontSize: 18,
                        fontWeight: FontWeight.w600,
                      ),
                    ),
                  ],
                ),
              ),
            );
          }).toList(),
        ),
      ],
    );
  }

  // 判断两个日期是否为同一天(忽略时分秒)
  bool isSameDate(DateTime? a, DateTime? b) {
    if (a == null || b == null) return false;
    return a.year == b.year && a.month == b.month && a.day == b.day;
  }
}

// 使用示例
class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('自定义周历')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: CustomWeekCalendar(
          initialDate: DateTime.now(),
          cellBgColor: Colors.grey[200]!,
          selectedCellBgColor: Colors.green,
          cellBorderRadius: 12,
          onDateSelected: (date) {
            print('选中日期:${DateFormat('yyyy-MM-dd').format(date)}');
          },
        ),
      ),
    );
  }
}

关键功能说明

  • 周切换:通过左右箭头按钮切换前后周,每次调整起始日期并刷新日期列表
  • 自定义样式:支持传入单元格背景色、选中状态背景色、圆角半径,可根据需求修改装饰样式(如添加边框、阴影)
  • 日期选中:点击单元格切换选中状态,同时支持回调外部处理选中事件
  • 周起始调整:如需将周日设为周起始,修改_getWeekStart方法中的weekday - 1为weekday % 7即可

内容的提问来源于stack exchange,提问作者Quang Hồ Vũ Minh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 21:32:30