flutter: table calendar笔记

pub dev:table_calendar 3.2.0
我来详细解释 TableCalendar 是如何根据不同的 CalendarFormat 来显示界面的。主要逻辑在 CalendarCore 中实现。
核心逻辑分为以下几个部分:

  1. 页面数量计算 - _getPageCount 方法根据不同格式计算总页数:
int _getPageCount(CalendarFormat format, DateTime first, DateTime last) {switch (format) {case CalendarFormat.month:return _getMonthCount(first, last) + 1;case CalendarFormat.twoWeeks:return _getTwoWeekCount(first, last) + 1;case CalendarFormat.week:return _getWeekCount(first, last) + 1;}
}
  1. 可见日期范围计算 - _getVisibleRange 方法决定每种格式显示多少天:
DateTimeRange _getVisibleRange(CalendarFormat format, DateTime focusedDay) {switch (format) {case CalendarFormat.month:return _daysInMonth(focusedDay);  // 显示整月case CalendarFormat.twoWeeks:return _daysInTwoWeeks(focusedDay);  // 显示两周case CalendarFormat.week:return _daysInWeek(focusedDay);  // 显示一周}
}
  1. 行数计算 - _getRowCount 方法决定显示多少行:
int _getRowCount(CalendarFormat format, DateTime focusedDay) {if (format == CalendarFormat.twoWeeks) {return 2;  // 两周显示2行} else if (format == CalendarFormat.week) {return 1;  // 一周显示1行} else if (sixWeekMonthsEnforced) {return 6;  // 月视图强制6行}// 月视图动态计算行数final first = _firstDayOfMonth(focusedDay);final daysBefore = _getDaysBefore(first);final firstToDisplay = first.subtract(Duration(days: daysBefore));final last = _lastDayOfMonth(focusedDay);final daysAfter = _getDaysAfter(last);final lastToDisplay = last.add(Duration(days: daysAfter));return (lastToDisplay.difference(firstToDisplay).inDays + 1) ~/ 7;
}
  1. 日期生成 - 根据不同格式生成要显示的日期:
// 生成一周的日期
DateTimeRange _daysInWeek(DateTime focusedDay) {final daysBefore = _getDaysBefore(focusedDay);final firstToDisplay = focusedDay.subtract(Duration(days: daysBefore));final lastToDisplay = firstToDisplay.add(const Duration(days: 7));return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
}// 生成两周的日期
DateTimeRange _daysInTwoWeeks(DateTime focusedDay) {final daysBefore = _getDaysBefore(focusedDay);final firstToDisplay = focusedDay.subtract(Duration(days: daysBefore));final lastToDisplay = firstToDisplay.add(const Duration(days: 14));return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
}// 生成一个月的日期
DateTimeRange _daysInMonth(DateTime focusedDay) {final first = _firstDayOfMonth(focusedDay);final daysBefore = _getDaysBefore(first);final firstToDisplay = first.subtract(Duration(days: daysBefore));if (sixWeekMonthsEnforced) {// 强制显示6周final end = firstToDisplay.add(const Duration(days: 42));return DateTimeRange(start: firstToDisplay, end: end);}// 根据实际天数显示final last = _lastDayOfMonth(focusedDay);final daysAfter = _getDaysAfter(last);final lastToDisplay = last.add(Duration(days: daysAfter));return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
}
  1. 页面切换逻辑 - _getFocusedDay 方法处理不同格式下的页面切换:
DateTime _getFocusedDay(CalendarFormat format,DateTime prevFocusedDay,int pageIndex,
) {if (pageIndex == previousIndex) {return prevFocusedDay;}final pageDif = pageIndex - previousIndex!;DateTime day;switch (format) {case CalendarFormat.month:// 月视图按月切换day = DateTime.utc(prevFocusedDay.year, prevFocusedDay.month + pageDif);case CalendarFormat.twoWeeks:// 两周视图每次移动14天day = DateTime.utc(prevFocusedDay.year,prevFocusedDay.month,prevFocusedDay.day + pageDif * 14,);case CalendarFormat.week:// 周视图每次移动7天day = DateTime.utc(prevFocusedDay.year,prevFocusedDay.month,prevFocusedDay.day + pageDif * 7,);}// 确保不超出范围if (day.isBefore(firstDay)) {day = firstDay;} else if (day.isAfter(lastDay)) {day = lastDay;}return day;
}

如果你想添加新的 CalendarFormat(比如 threeWeeks),需要:

  • 在 utils.dart 中的 CalendarFormat 枚举中添加新格式
  • 在上述所有 switch 语句中添加新的 case 处理
  • 实现对应的日期范围计算方法
  • 添加行数计算逻辑

例如添加 threeWeeks 格式

// 1. 在 CalendarFormat 中添加
enum CalendarFormat { month, twoWeeks, week, threeWeeks }// 2. 在 switch 语句中添加处理
case CalendarFormat.threeWeeks:return _daysInThreeWeeks(focusedDay);// 3. 实现日期范围计算
DateTimeRange _daysInThreeWeeks(DateTime focusedDay) {final daysBefore = _getDaysBefore(focusedDay);final firstToDisplay = focusedDay.subtract(Duration(days: daysBefore));final lastToDisplay = firstToDisplay.add(const Duration(days: 21));return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
}// 4. 添加行数计算
if (format == CalendarFormat.threeWeeks) {return 3;  // 三周显示3行
}

TableCalendar 的界面绘制流程

让我详细解释 TableCalendar 的界面绘制流程。整个日历界面的绘制主要涉及以下组件的配合:
TableCalendar (主组件) -> CalendarCore (核心布局) -> CalendarPage (单页渲染)
让我们从最内层的 CalendarPage 开始解释:

  1. CalendarPage - 单个日历页面的渲染
class CalendarPage extends StatelessWidget {@overrideWidget build(BuildContext context) {return Row(children: [// 1. 可选的周数显示列if (weekNumberVisible) _buildWeekNumbers(context),// 2. 主日历表格Expanded(child: Table(border: tableBorder,children: [// 2.1 星期头部行 (Mon, Tue, Wed...)if (dowVisible) _buildDaysOfWeek(context),// 2.2 日期行..._buildCalendarDays(context),],),),],);}
}

日历页面使用 Table widget 来布局,包含:
可选的周数列
星期头部行
日期网格
2. CalendarCore - 页面管理与切换

class CalendarCore extends StatelessWidget {@overrideWidget build(BuildContext context) {return PageView.builder(controller: pageController,itemCount: _getPageCount(calendarFormat, firstDay, lastDay),itemBuilder: (context, index) {// 1. 计算这一页要显示的基准日期final baseDay = _getBaseDay(calendarFormat, index);// 2. 计算这一页要显示的日期范围final visibleRange = _getVisibleRange(calendarFormat, baseDay);// 3. 生成要显示的所有日期final visibleDays = _daysInRange(visibleRange.start, visibleRange.end);// 4. 计算行高final actualDowHeight = dowVisible ? dowHeight! : 0.0;final constrainedRowHeight = constraints.hasBoundedHeight? (constraints.maxHeight - actualDowHeight) / _getRowCount(calendarFormat, baseDay): null;// 5. 渲染日历页面return CalendarPage(visibleDays: visibleDays,// ... 其他配置);},);}
}
  1. 实际的渲染过程
    以月视图为例,完整的渲染流程:
// 1. 确定要显示的日期范围
DateTimeRange _daysInMonth(DateTime focusedDay) {// 获取月份第一天final first = _firstDayOfMonth(focusedDay);  // 例如:2024-03-01// 计算需要显示的上月剩余天数final daysBefore = _getDaysBefore(first);    // 如果3月1日是周五,需要显示前4天// 计算本月第一个显示的日期final firstToDisplay = first.subtract(Duration(days: daysBefore));  // 2024-02-26if (sixWeekMonthsEnforced) {// 强制显示6周final end = firstToDisplay.add(const Duration(days: 42));  // 显示42天return DateTimeRange(start: firstToDisplay, end: end);}// 获取月份最后一天final last = _lastDayOfMonth(focusedDay);    // 2024-03-31// 计算需要显示的下月天数final daysAfter = _getDaysAfter(last);       // 补充到完整的周// 计算最后一个显示的日期final lastToDisplay = last.add(Duration(days: daysAfter));  // 2024-04-06return DateTimeRange(start: firstToDisplay, end: lastToDisplay);
}// 2. 生成所有要显示的日期
List<DateTime> _daysInRange(DateTime first, DateTime last) {final dayCount = last.difference(first).inDays + 1;return List.generate(dayCount,(index) => DateTime.utc(first.year, first.month, first.day + index),);
}// 3. 渲染表格
List<TableRow> _buildCalendarDays(BuildContext context) {final rowAmount = visibleDays.length ~/ 7;  // 计算行数return List.generate(rowAmount,(row) => TableRow(decoration: rowDecoration,children: List.generate(7,(column) => dayBuilder(context, visibleDays[row * 7 + column]),),),);
}

视觉效果示意:

     March 2024
Mo Tu We Th Fr Sa Su
26 27 28 29  1  2  3  <- 上月日期4  5  6  7  8  9 10  <- 本月第一周
11 12 13 14 15 16 17  <- 本月第二周
18 19 20 21 22 23 24  <- 本月第三周
25 26 27 28 29 30 31  <- 本月第四周1  2  3  4  5  6  7  <- 下月日期

不同的 CalendarFormat 只是改变了显示的行数:

  • CalendarFormat.month: 显示整月(4-6行)
  • CalendarFormat.twoWeeks: 显示2行
  • CalendarFormat.week: 显示1行
    每个日期单元格的具体样式由 dayBuilder 控制,可以通过 CalendarBuilders 和 CalendarStyle 来自定义外观。
    这就是整个 TableCalendar 的界面渲染流程。它通过 PageView 实现翻页,通过 Table 实现网格布局,通过精确的日期计算确保日历的正确显示

其中函数_buildCalendarDays的解释如下:
让我们一步步解析:

  1. 函数声明:
    • List 表示返回一个 TableRow 类型的列表
      List.generate(count, (index) => value)
      下划线 _ 开头表示这是一个私有方法
      BuildContext context 是 Flutter 中用于构建 Widget 的上下文参数
  2. 行数计算:
   final rowAmount = visibleDays.length ~/ 7;
- ~/ 是整数除法运算符
比如如果 visibleDays.length 是 35,那么 rowAmount 就是 5
  1. List.generate: List.generate(count, (index) => value)
    这是 Dart 的列表生成方法
    count 指定要生成多少个元素
    (index) => value 是一个函数,用于生成每个元素
  2. TableRow:
    • Flutter 中表格的一行
      decoration 用于设置行的样式(比如背景色)
      children 包含这一行的所有单元格
  3. 嵌套的 List.generate:
    • 生成每行的 7 个单元格
      index * 7 + id 计算当前单元格对应的日期索引
      dayBuilder 用于构建每个日期的显示内容

举个例子,如果你要显示一个月的日历:
假设有 35 天要显示(5 周)
rowAmount 将是 5(35/7)
外层 List.generate 会生成 5 行
每行内部的 List.generate 会生成 7 个单元格
最终生成一个 5×7 的表格
这就像在创建一个 Excel 表格:
每个单元格的具体显示内容由 dayBuilder 决定,这就是为什么它是一个可自定义的函数。

生成视图的流程

  1. 初始化阶段:
// 在测试代码中初始化 TableCalendarBase
TableCalendarBase(dayBuilder: (context, day, focusedDay) {return Text('${day.day}',key: dateToKey(day),);},// ... 其他参数
)
  1. CalendarCore 中的包装:
// CalendarCore 给每个日期添加固定高度的容器
dayBuilder: (context, day) {// ... 计算 baseDay ...return SizedBox(height: constrainedRowHeight ?? rowHeight,child: dayBuilder(context, day, baseDay),  // 调用原始的 dayBuilder);
}
  1. CalendarPage 中的布局:
// CalendarPage 将日期排列成表格
List<TableRow> _buildCalendarDays(BuildContext context) {return List.generate(rowAmount,  // 行数(index) => TableRow(children: List.generate(7,  // 每行7列(id) => dayBuilder(context, visibleDays[index * 7 + id]),  // 调用包装后的 dayBuilder),),);
}

所以完整流程是:
用户提供基础的日期显示方式(Text组件)
CalendarCore 添加大小控制(SizedBox)
CalendarPage 将所有日期组织成表格形式(Table和TableRow)
最终形成一个完整的日历视图
这就像搭积木:
Text(显示日期)
→ SizedBox(控制大小)
→ TableRow(排成一行)
→ Table(组成表格)
→ 完整日历

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/70685.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】各个版本新的特性和改进

C 语言自从其诞生以来&#xff0c;经历了多个版本的更新&#xff0c;每个版本都引入了新的特性和改进&#xff0c;目的是提升语言的表达能力、性能、安全性以及开发效率。下面是各个主要版本&#xff08;从 C98 到 C20&#xff09;的一些关键特性。 C98 (1998年) ISO C 标准化…

C++模板与STL七日斩:从工业编程到高效数据管理(工业项目)

模板如何提升工业代码复用性 实战项目&#xff1a;创建通用【工业设备容器】模板类 类模板的定义与实例化模板参数默认值 #include <iostream> #include <string> using namespace std;template <typename T string> class IndustrialContainer { priva…

sh脚本把服务器B,服务器C目录的文件下载到服务器A目录,添加开机自启动并且一小时执行一次脚本

脚本逻辑 第一次会下载,第二次比较如果有就不下载 文件已存在&#xff1a; 如果目标目录中已经存在同名文件&#xff0c;rsync 会比较源文件和目标文件的大小和修改时间。 如果源文件和目标文件的大小和修改时间完全相同&#xff0c;rsync 会跳过该文件&#xff0c;不会重新下载…

云手机如何进行经纬度修改

云手机如何进行经纬度修改 云手机修改经纬度的方法因不同服务商和操作方式有所差异&#xff0c;以下是综合多个来源的常用方法及注意事项&#xff1a; 通过ADB命令注入GPS数据&#xff08;适用于技术用户&#xff09; 1.连接云手机 使用ADB工具连接云手机服务器&#xff0c;…

透彻理解:方差、协方差、相关系数、协方差矩阵及其应用

最近看了几篇跨领域特征对齐方面的经典文献&#xff0c;学者们搞了很多花样&#xff0c;如有的提出一阶统计特征对齐&#xff0c;有的提出二阶统计特征对齐&#xff0c;有的学者提出高阶统计特征对齐。 通俗而言&#xff0c;就是在统计特征层面对跨域特征进行对齐&#xff0c;…

Unity基础学习(二)

二、Mono中的重要内容 1、延迟函数 &#xff08;1&#xff09;延迟函数定义 延迟执行的函数&#xff0c;可以设定要延迟执行的函数和具体延迟的时间 &#xff08;2&#xff09;延迟函数的使用 #region 1、延迟函数//函数&#xff1a;Invoke(函数名/字符串&#xff0c;延迟时…

20250212:ZLKMedia 推流

1:资料 快速开始 ZLMediaKit/ZLMediaKit Wiki GitHub GitHub - ZLMediaKit/ZLMediaKit: WebRTC/RTSP/RTMP/HTTP/HLS/HTTP-FLV/WebSocket-FLV/HTTP-TS/HTTP-fMP4/WebSocket-TS/WebSocket-fMP4/GB28181/SRT server and client framework based on C++11 文档里面提供了各个系…

Holoens2开发报错记录02_通过主机获取彩色和深度数据流常见错误

01.E1696 E1696 无法打开源文件 “stdio.h” 解决方法&#xff1a; 更新一下SDK 1&#xff09;打开Visual Studio Installer&#xff0c;点击修改 2&#xff09;安装详细信息中自己系统对应的SDK&#xff0c;点击修改即可 02.WinError 10060 方法来源 解决方法&#xff1a…

【Qt之QQuickWidget】QML嵌入QWidget中

由于我项目开始使用Widgets,换公司后直接使用QML开发&#xff0c;没有了解过如何实现widget到qml过渡&#xff0c;恰逢面试时遇到一家公司希望从widget迁移到qml开发&#xff0c;询问相关实现&#xff0c;一时语塞&#xff0c;很尴尬&#xff0c;粗略研究并总结下。 对qwidget嵌…

从单片机的启动说起一个单片机到点灯发生了什么下——使用GPIO点一个灯

目录 前言 HAL库对GPIO的抽象 核心分析&#xff1a;HAL_GPIO_Init 前言 我们终于到达了熟悉的地方&#xff0c;对GPIO的初始化。经过漫长的铺垫&#xff0c;我们终于历经千辛万苦&#xff0c;来到了这里。关于GPIO的八种模式等更加详细的细节&#xff0c;由于只是点个灯&am…

ESP32S3:解决RWDT无法触发中断问题,二次开发者怎么才能使用内部RTC看门狗中断RWDT呢?

目录 基于ESP32S3:解决RWDT无法触发中断问题引言解决方案1. 查看报错日志2. 分析报错及一步一步找到解决方法3.小结我的源码基于ESP32S3:解决RWDT无法触发中断问题 引言 在嵌入式系统中,RWDT(看门狗定时器)是确保系统稳定性的重要组件。然而,在某些情况下,RWDT可能无法…

对计算机中缓存的理解和使用Redis作为缓存

使用Redis作为缓存缓存例子缓存的引入 Redis缓存的实现 使用Redis作为缓存 缓存 ​什么是缓存&#xff0c;第一次接触这个东西是在考研学习408的时候&#xff0c;计算机组成原理里面学习到Cache缓存&#xff0c;用于降低由于内存和CPU的速度的差异带来的延迟。它是在CPU和内存…

vue3的实用工具库@vueuse/core

1.什么是vueuse/core 是一个基于 ‌Vue Composition API‌ 开发的实用工具库&#xff0c;旨在通过封装高频功能为可复用的组合式函数&#xff08;Composables&#xff09;&#xff0c;简化 Vue 应用的开发流程。 提供 ‌200 开箱即用的函数‌&#xff0c;覆盖状态管理、浏览器…

基于SSM的《计算机网络》题库管理系统(源码+lw+部署文档+讲解),源码可白嫖!

摘 要 《计算机网络》题库管理系统是一种新颖的考试管理模式&#xff0c;因为系统是用Java技术进行开发。系统分为三个用户进行登录并操作&#xff0c;分别是管理员、教师和学生。教师在系统后台新增试题和试卷&#xff0c;学生进行在线考试&#xff0c;还能对考生记录、错题…

C++初阶——简单实现stack和queue

目录 1、Deque(了解) 1.1 起源 1.2 结构 1.3 优缺点 1.4 应用 2、Stack 3、Queue 4、Priority_Queue 注意&#xff1a;stack&#xff0c;queue&#xff0c;priority_queue是容器适配器(container adaptor) &#xff0c;封装一个容器&#xff0c;按照某种规则使用&#…

第2课 树莓派镜像的烧录

树莓派的系统通常是安装在SD卡上的‌。SD卡作为启动设备,负责启动树莓派并加载操作系统。这种设计使得树莓派具有便携性和灵活性,用户可以通过更换SD卡来更换操作系统或恢复出厂设置。 烧录树莓派的镜像即是将树莓派镜像烧录到SD卡上,在此期间会格式化SD卡,如果SD卡…

【Unity】URP管线Shader编程实例详解 (1) : 漩涡效果shader

作者说 本系列教程适用于有编程基础和图形学基础知识的读者.如果对您有所帮助&#xff0c;请点个免费的赞和关注&#xff0c;您的支持就是我更新最大的动力&#xff01;如果你有任何想看的内容欢迎评论区留言&#xff01;本系列教程Github : https://github.com/Sky0Master/Un…

如何安装vm 和centos

安装 VMware Workstation&#xff08;以 Windows 系统为例&#xff09; 1. 下载 VMware Workstation 打开 VMware 官方网站&#xff08;Desktop Hypervisor Solutions | VMware &#xff09;&#xff0c;在页面中选择适合你系统的版本进行下载。如果你是个人非商业使用&#x…

STM32-心知天气项目

一、项目需求 使用 ESP8266 通过 HTTP 获取天气数据&#xff08;心知天气&#xff09;&#xff0c;并显示在 OLED 屏幕上。 按键 1 &#xff1a;循环切换今天 / 明天 / 后天天气数据&#xff1b; 按键 2 &#xff1a;更新天气。 二、项目框图 三、cjson作用 https://gi…

Wireshark简单教程

1.打开Wireshark,点击最上面栏目里面的“捕获”中的“选项” 2.进入网卡选择界面,选择需要捕获的选择&#xff0c;这里我选择WLAN 3.双击捕获选择出现下面界面 4.点击如下图红方框即可停止捕获 5.点击下图放大镜可以进行放大 6.你也可以查询tcp报文如下图