flutter自定义table_calendar

762 阅读6分钟

flutter自定义table_calendar

没想到吧,我又开始写flutter了,因为我现在是写海外APP的,uniapp虽然简单快捷,但是对于海外某些sdk的接入难度会直线飙升,原生集成,封装原生方法,封装原生视图之类的,本来是个web前端小菜鸡,不会Android,现在也算是略懂一点点Android了。因为海外很多sdk对flutter的支持度还算可以,又因之前闲暇之余有搞过flutter Hello World 的项目经历,加之二月到六月因为工作需要,将一个Android app转鸿蒙原生开发,所以熟悉flutter也还算快,所以就开始搞flutter开发了。

扯远了,进入正题,因为项目涉及到日历相关的,就在网上找了一下,最后选择了 table_calendar,因为这个出了新的,删除了某些api,又新增了某些api,导致部分自定义功能搜索出来的代码还是之前的版本,所以最后参考官网实现了,做个简单的记录,方便后续查阅(其实官方文档里面都有......)。

1. 基本使用

按照官方文档,写入必要的一些参数,就行了

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  DateTime currentTime = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: TableCalendar(
          // 可选时间区间的开始时间
          firstDay: DateTime.utc(2000, 1, 1),
          // 可选时间区间的结束时间
          lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
          // 当前获取焦点的时间
          focusedDay: currentTime,
        ),
      ),
    );
  }
}

这样就得到了一个很简单的日历组件

image01.png

接下来我们尝试更改一些基本的样式

1.1 修改头部样式

使用 headerStyle 属性,我们可以很快的更改头部的样式

// 其他代码就省略...
TableCalendar(
  // 可选时间区间的开始时间
  firstDay: DateTime.utc(2000, 1, 1),
  // 可选时间区间的结束时间
  lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
  // 当前获取焦点的时间
  focusedDay: currentTime,
  headerStyle: const HeaderStyle(
    // 将头部的padding和margin设置为0
    headerPadding: EdgeInsets.zero,
    headerMargin: EdgeInsets.zero,
    // 设置头部显示的日期居中
    titleCentered: true,
    // 设置头部显示的日期文本样式
    titleTextStyle: TextStyle(
      fontSize: 20,
      fontWeight: FontWeight.bold
    ),
    // 隐藏formatButton
    formatButtonVisible: false,
    // 设置左右切换按钮的margin
    leftChevronMargin: EdgeInsets.only(left: 40),
    rightChevronMargin: EdgeInsets.only(right: 40)
  )
),

当然还可以设置一些其他的,基本上见名知意

image02.png

1.2 修改星期栏的样式

使用 daysOfWeekStyledaysOfWeekHeight 可以很好的更改其样式

// 其他代码就省略...
TableCalendar(
  // 可选时间区间的开始时间
  firstDay: DateTime.utc(2000, 1, 1),
  // 可选时间区间的结束时间
  lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
  // 当前获取焦点的时间
  focusedDay: currentTime,
  // 设置展示星期栏的高度
  daysOfWeekHeight: 40,
  // 设置展示星期栏的样式
  daysOfWeekStyle: const DaysOfWeekStyle(
    // 设置星期一到星期五的文本样式
    weekdayStyle: TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.bold,
      color: Color(0xFF666666)
    ),
    // 设置星期六和星期天的文本样式
    weekendStyle: TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.bold,
      color: Color(0xFFFF0000)
    ),
    // 设置星期容器的样式
    decoration: BoxDecoration(
      color: Color(0xFFC0C0C0),
      border: Border(
        bottom: BorderSide(
          width: 1,
          color: Color(0xFF000000)
        )
      )
    )
  ),
),

其他属性可以自行设置体验

image03.png

1.3 修改日历日期的样式

TableCalendar(
  // 可选时间区间的开始时间
  firstDay: DateTime.utc(2000, 1, 1),
  // 可选时间区间的结束时间
  lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
  // 当前获取焦点的时间
  focusedDay: currentTime,
  // 设置日历横行的高度
  rowHeight: 50,
  calendarStyle: const CalendarStyle(
    // 设置除周末外的日历文本样式
    defaultTextStyle: TextStyle(
      fontSize: 20,
      color: Color(0xFF666666),
      fontWeight: FontWeight.w600
    ),
    // 设置周末的文本样式
    weekendTextStyle: TextStyle(
      fontSize: 20,
      color: Color(0xFFFF0000),
      fontWeight: FontWeight.bold
    ),
    // 设置当前日期的文本样式
    todayTextStyle: TextStyle(
      fontSize: 20,
      fontWeight: FontWeight.bold,
      color: Color(0xFFFFFFFF)
    ),
    // 设置当前日期的容器样式
    todayDecoration: BoxDecoration(
      color: Color(0xFFFF657D),
      shape: BoxShape.circle,
    )
  ),
),

其他属性可以自行设置体验

image04.png

通过以上属性就可以更改大部分样式,接下来我们使用其他属性进行更加精细的自定义

2. 精细自定义

2.1 自定义头部容器

如果我要对头部新增一些按钮图标,或者控制某些样式,这样我们就需要使用 calendarBuildersheaderStyle 属性来自定义头部

class _MyHomePageState extends State<MyHomePage> {
  DateTime currentTime = DateTime.now();
  // 用于自定义切换上一月和下一月
  late final PageController _pageController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: TableCalendar(
          // 监听日历组件创建,设置_pageController
          onCalendarCreated: (pageController) {
            _pageController = pageController;
          },
          // 可选时间区间的开始时间
          firstDay: DateTime.utc(2000, 1, 1),
          // 可选时间区间的结束时间
          lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
          // 当前获取焦点的时间
          focusedDay: currentTime,
          calendarBuilders: CalendarBuilders(
            // 所以自定义头部Builder,我们就可以自定义头部小部件了
            headerTitleBuilder: (context, dateTimeInfo) {
              return Column(
                children: [
                  Stack(
                    children: [
                      Positioned(
                        left: 20,
                        top: 3,
                        child: GestureDetector(
                          onTap: () {
                            // 处理事件
                          },
                          child: Image.asset(
                            'assets/images/icon_to_record.png',
                            width: 26,
                            height: 26,
                          ),
                        ),
                      ),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.center,
                        children: [
                          // 自定义切换上一月按钮
                          GestureDetector(
                            onTap: () {
                              // 切换上一月
                              _pageController.previousPage(
                                duration: const Duration(milliseconds: 300),
                                curve: Curves.easeOut,
                              );
                            },
                            child: SizedBox(
                              width: 20,
                              height: 20,
                              child: Image.asset(
                                'assets/images/icon_back.png',
                                width: 6,
                                height: 11,
                              ),
                            ),
                          ),
                          const SizedBox(width: 20,),
                          Text(
                            '${dateTimeInfo.month}-${dateTimeInfo.year}',
                            style: const TextStyle(
                                fontSize: 24,
                                fontWeight: FontWeight.bold
                            ),
                          ),
                          const SizedBox(width: 20,),
                          // 自定义切换上一月按钮
                          GestureDetector(
                              onTap: () {
                                // 切换上一月
                                _pageController.nextPage(
                                  duration: const Duration(milliseconds: 300),
                                  curve: Curves.easeOut,
                                );
                              },
                              child: Transform(
                                alignment: Alignment.center,
                                transform: Matrix4.identity()..rotateZ(math.pi),
                                child: SizedBox(
                                  width: 20,
                                  height: 20,
                                  child: Image.asset(
                                    'assets/images/icon_back.png',
                                    width: 6,
                                    height: 11,
                                  ),
                                ),
                              )
                          ),
                        ],
                      )
                    ],
                  ),
                  Container(
                    width: 200,
                    height: 40,
                    decoration: BoxDecoration(
                      color: const Color(0x33000000),
                      borderRadius: BorderRadius.circular(20)
                    ),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        const Text(
                          '这是二排文本',
                          style: TextStyle(
                            fontSize: 16,
                            fontWeight: FontWeight.w600
                          ),
                        ),
                        const SizedBox(width: 10,),
                        GestureDetector(
                          onTap: () {
                            // 处理点击事件
                          },
                          child: const SizedBox(
                            width: 20,
                            height: 20,
                            child: Text('+', style: TextStyle(fontSize: 16),),
                          ),
                        )
                      ],
                    ),
                  )
                ],
              );
            }
          ),
          headerStyle: const HeaderStyle(
            titleCentered: true,
            // 隐藏formatButton
            formatButtonVisible: false,
            // 因为我们要自定义布局,所以隐藏这两个按钮,默认这两个按钮等分剩余的宽度
            // 隐藏左边切换上一月按钮
            leftChevronVisible: false,
            // 隐藏右边切换下一月按钮
            rightChevronVisible: false
          ),
        ),
      ),
    );
  }
}

image05.png

2.2 自定义选中日期

TableCalendar(
  // 可选时间区间的开始时间
  firstDay: DateTime.utc(2000, 1, 1),
  // 可选时间区间的结束时间
  lastDay: DateTime.utc(DateTime.now().year + 10, 12, 31),
  // 当前获取焦点的时间
  focusedDay: currentTime,
  // 设置当前选中日期
  selectedDayPredicate: (day) => isSameDay(currentTime, day),
  calendarStyle: const CalendarStyle(
    // 设置当前日期的样式
    todayDecoration: BoxDecoration(
      color: Colors.amber,
      shape: BoxShape.circle,
    ),
    // 设置选中的日期容器样式
    selectedDecoration: BoxDecoration(
      color: Color(0xFFFF657D),
      shape: BoxShape.circle,
    ),
  ),
  // 点击选中日期
  onDaySelected: (selectedDate, focusedDay) {
    setState(() {
      currentTime = selectedDate;
    });
  },
),

image06.png

至此,结束,后续还需要自定义还可以参照文档更改即可,就目前这样也可以满足很多场景了。