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 {
  @override
  Widget 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 {
  @override
  Widget 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-26
  
  if (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-06

  return 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(组成表格)
→ 完整日历

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python 网络爬虫实战全解析:案例驱动的技术探索

Python 网络爬虫实战全解析&#xff1a;案例驱动的技术探索 本文围绕 Python 网络爬虫展开&#xff0c;深入剖析其技术要点&#xff0c;并通过实际案例演示开发流程。从爬虫原理引入&#xff0c;逐步讲解如何使用 Python 中的requests和BeautifulSoup等库进行网页数据抓取与解…

List(3)

前言 上一节我们讲解了list主要接口的模拟实现&#xff0c;本节也是list的最后一节&#xff0c;我们会对list的模拟实现进行收尾&#xff0c;并且讲解list中的迭代器失效的情况&#xff0c;那么废话不多说&#xff0c;我们正式进入今天的学习 list的迭代器失效 之前在讲解vec…

在zotero里部署papaerschat插件,以接入现有大模型

papaerschat插件里集成了openAI的GPT3.5、gpt-4o、gpt-mini大模型以及Claude3、Gemini、Deepseek等大模型。通过接入这些大模型可以辅助我们阅读论文。以部署方式如下&#xff1a; 1.下载zotero的插件市场&#xff0c;用以管理zotero里的插件。下载地址&#xff1a; https://…

Memory Programming ...Error: File does not exist: Max.hex

Memory Programming ... Error: File does not exist: Max.hex 原因 删了确定就可以了

渗透测试【seacms V9】

搭建seacms环境 我选择在虚拟机中用宝塔搭建环境 将在官网选择的下载下来的文件解压后拖入宝塔面板的文件中 创建网站 添加站点 搭建完成seacmsV9 找到一个报错口 代码分析 <?php set_time_limit(0); error_reporting(0); $verMsg V6.x UTF8; $s_lang utf-8; $dfDbn…

仅需三分钟,使用Vue3.x版本组件式风格实现一个消息提示组件!

一、前言 在日常的前端项目开发中&#xff0c;我们时常需要使用到“消息提示”&#xff08;以下简称“消息”&#xff09;这个组件来帮助我们更好的给予用户提示&#xff0c;例如常见的“登录成功”、“操作成功”、“服务器异常”等等提示。 尽管市面上已经有一些组件库提供了…

敏捷开发实践指南:从理论到落地的全面解析

敏捷工程&#xff1a;现代软件开发的变革与实践 近年来&#xff0c;软件工程领域经历了从传统瀑布模型到敏捷开发的深刻转变。这种转变不仅是技术方法的升级&#xff0c;更是团队协作、需求管理和交付模式的革新。本文将从敏捷开发的核心理念、主流方法、实践案例及未来趋势等…

期权帮|股指期货基差和价差有什么区别?

锦鲤三三每日分享期权知识&#xff0c;帮助期权新手及时有效地掌握即市趋势与新资讯&#xff01; 股指期货基差和价差有什么区别&#xff1f; 一、股指期货基差 股指期货基差是指股指期货价格与其对应的现货指数价格之间的差额。 股指期货基差计算公式&#xff1a;基差 现…

【论文解读】《C-Pack: Packed Resources For General Chinese Embeddings》

论文链接&#xff1a;https://arxiv.org/pdf/2309.07597 本论文旨在构建一套通用中文文本嵌入的完整资源包——C-Pack&#xff0c;解决当前中文文本嵌入研究中数据、模型、训练策略与评测基准缺失的问题。论文主要贡献体现在以下几个方面&#xff1a; 大规模训练数据&#xf…

ARM 处理器平台 eMMC Flash 存储磨损测试示例

By Toradex秦海 1). 简介 目前工业嵌入式 ARM 平台最常用的存储器件就是 eMMC Nand Flash 存储&#xff0c;而由于工业设备一般生命周期都比较长&#xff0c;eMMC 存储器件的磨损寿命对于整个设备来说至关重要&#xff0c;因此本文就基于 NXP i.MX8M Mini ARM 处理器平台演示…

html中的元素(2)

在用块级元素完成网页的组织和布局以后&#xff0c;要为其中的每一个小区块添加内容&#xff0c;就需要用到行内元素&#xff1a; 1.字体样式元素 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>HTML5 保留的文本格式元…

代码随想录二刷|动态规划12

dp动态规划 动态规划五步曲 动态规划数组的含义 dp[i] 递推公式 动态规划数组的初始化 确定遍历顺序 手动模拟验证 动态规划遇到问题要打印dp数组&#xff0c;看和模拟结果哪里不一样 一 基础问题 斐波那契数 题干 斐波那契数 &#xff08;通常用 F(n) 表示&#xf…

linux 系统 安装禅道教程

禅道&#xff08;ZenTao&#xff09;是一款开源的项目管理软件&#xff0c;特别适用于敏捷开发和团队协作。它集成了需求管理、任务管理、缺陷管理、版本管理、文档管理等功能&#xff0c;旨在帮助团队更高效地管理项目&#xff0c;提升工作协同和开发效率。 禅道的主要特点&a…

CineMaster: 用于电影文本到视频生成的 3D 感知且可控的框架。

CineMaster是一种 3D 感知且可控的文本到视频生成方法允许用户在 3D 空间中联合操纵物体和相机&#xff0c;以创作高质量的电影视频。 相关链接 论文&#xff1a;cinemaster-dev.github.io 论文介绍 CineMaster是一种用于 3D 感知和可控文本到视频生成的新型框架。目标是让用…

Linux红帽:RHCSA认证知识讲解(四)修改远程配置文件,取消root禁用,便于使用root身份远程

Linux红帽&#xff1a;RHCSA认证知识讲解&#xff08;四&#xff09;修改远程配置文件&#xff0c;取消root禁用&#xff0c;便于使用root身份远程 前言一、远程连接的用途和原因二、通过 ssh 远程登陆系统三、默认限制及解决方案&#xff08;一&#xff09;非常规方法一&#…

OpenEuler学习笔记(三十五):搭建代码托管服务器

以下是主流的代码托管软件分类及推荐&#xff0c;涵盖自托管和云端方案&#xff0c;您可根据团队规模、功能需求及资源情况选择&#xff1a; 一、自托管代码托管平台&#xff08;可私有部署&#xff09; 1. GitLab 简介: 功能全面的 DevOps 平台&#xff0c;支持代码托管、C…

Rk3568驱动开发_点亮led灯(手动挡)_5

1.MMU简介 完成虚拟空间到物理空间的映射 内存保护设立存储器的访问权限&#xff0c;设置虚拟存储空间的缓冲特性 stm32点灯可以直接操作寄存器&#xff0c;但是linux点灯不能直接访问寄存器&#xff0c;linux会使能mmu linux中操作的都是虚拟地址&#xff0c;要想访问物理地…

免费使用 DeepSeek API 教程及资源汇总

免费使用 DeepSeek API 教程及资源汇总 一、DeepSeek API 资源汇总1.1 火山引擎1.2 百度千帆1.3 阿里百炼1.4 腾讯云 二、其他平台2.1 华为云2.2 硅基流动 三、总结 DeepSeek-R1 作为 2025 年初发布的推理大模型&#xff0c;凭借其卓越的逻辑推理能力和成本优势&#xff0c;迅速…

QML Text部件的使用

一个简单的Text代码 Text {id: txttext: qsTr("文本123abc\n数量的")color: "blue" } 效果&#xff1a; Text一般用于显示文本&#xff0c;例如可以给Button或者Rectangle等部件提供文本的显示&#xff1b; 1.文本常用 contentWidth 文本的宽度…

《Android-RecyclerView实现封面滑动到指定位置放大》---ViewPager封面指示器

一、实现效果 二、关键代码 1、自定义:LinearLayoutManager 指定位置放大item import android.content.Context; import android.util.DisplayMetrics; import android.view.View; import android.view.ViewGroup;import androidx.recyclerview.widget.LinearLayoutManager;…