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.pswp.cn/web/70685.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/70685.shtml
英文地址,請注明出處:http://en.pswp.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報文如下圖