news 2026/8/20 21:16:34

深入源码:THCalendarDatePicker 日历网格布局算法是如何工作的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
深入源码:THCalendarDatePicker 日历网格布局算法是如何工作的?

深入源码:THCalendarDatePicker 日历网格布局算法是如何工作的?

【免费下载链接】THCalendarDatePickerA DatePicker based on a custom calendar view项目地址: https://gitcode.com/gh_mirrors/th/THCalendarDatePicker

THCalendarDatePicker 是一款基于自定义日历视图构建的 iOS 日期选择器(A DatePicker based on a custom calendar view)。很多开发者第一次打开它都会好奇:屏幕上那整齐的 7 列 × 6 行日历网格布局到底是怎么算出来的?每个月天数不同、一号落在星期几也不同,网格却永远不会乱。这篇文章将带你深入 THCalendarDatePicker 源码,逐行拆解日历网格布局算法的完整计算流程,无论你是新手还是想二次开发的工程师,都能快速看懂它的运作原理。

一、为什么日历网格布局是选择器的灵魂?

日历选择器的一切交互都建立在网格之上:日期被点击、被选中、被禁用、被标记圆点,全都发生在一个个等分的格子里。THCalendarDatePicker 选择了一个非常聪明的策略——网格永远固定为 7 列 × 6 行,一共 42 个格子,而不是像某些日历那样按月份实际周数动态伸缩。

固定网格带来两个好处:

  • 布局稳定:无论 2 月只有 28 天还是 7 月有 31 天,控件的高度和宽度都不变,界面不会抖动;
  • 渲染高效:每次翻页只需重绘固定数量的格子,配合滑动动画也不会出现明显的性能问题。

这套网格的核心逻辑全部集中在THCalendarDatePicker/THDatePickerViewController.m里,下面我们按“算参数 → 填格子 → 做对齐 → 管状态 → 加交互”五步来拆解。

二、网格布局三要素:7 列、6 行、起始偏移

打开redrawDays方法,你会发现网格的尺寸计算非常直接:

  • 列宽cellWidth = areaWidth / 7,把容器宽度平均分成 7 份,对应一周 7 天;
  • 行高cellHeight = areaHeight / _weeksOnCalendar,而行数_weeksOnCalendar被硬编码为6
  • 起始偏移_bufferDaysBeginning,记录本月 1 号之前需要预留的空格数量,这是整个布局算法里最关键的一个变量。
参数取值作用
列数7一周 7 天,cellWidth = areaWidth / 7
行数6(固定)_weeksOnCalendar = 6,共 42 格
起始偏移_bufferDaysBeginning本月 1 号前的空白格数量

三、起始空白格的计算方法:一号是星期几说了算

为什么有的月份 1 号在第一个格子,有的却要空出三四个格子?答案就在storeDateInformation方法里:

NSRange days = [c rangeOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitMonth forDate:self.firstOfCurrentMonth]; int bufferDaysBeginning = (int)([comps weekday]-[c firstWeekday]); if (bufferDaysBeginning < 0) bufferDaysBeginning += 7;

它的逻辑是:取本月 1 号的weekday(周日为 1,周六为 7),减去日历的firstWeekday(默认也是周日 1),差值就是要预留的空白格数;如果结果是负数,就加上 7 修正——源码注释里还特意提到,% 7对负数取模在 Objective-C 中行为怪异,所以用了if判断来兜底。

举个例子:2015 年 7 月 1 日是周三(weekday = 4),那么bufferDaysBeginning = 4 - 1 = 3,于是 7 月前面会先画出 28、29、30 三个上个月的浅灰色日期,正好对应上图截图里第一行的效果。📐

四、42 个日期格子如何被逐一填充?

拿到偏移量之后,redrawDays就开始“铺格子”了,整体流程可以概括为:

  1. 找起点:从firstOfCurrentMonth往回退_bufferDaysBeginning天,得到网格中第一个格子对应的日期;
  2. 循环 42 次:每次创建一个THDateDay视图(从THDateDay.xib加载),放进网格,并让它指向当前日期;
  3. 前进与换行:每处理一格curX += cellWidth;当索引i % 7 == 0时回到行首curX = origX,同时curY += cellHeight进入下一行;
  4. 写日期数字:取出日期的day分量,设置到按钮标题上。

核心循环长这样:

for(int i = 0; i < days; i++){ if(i && !(i%7)) { // 每 7 个换一行 curX = origX; curY += cellHeight; } THDateDay * day = [加载 THDateDay.xib]; day.frame = CGRectMake(curX, curY, cellWidth, cellHeight); day.date = date; // 设置标题、颜色、选中状态…… date = 加一天; // 日期向前推进 curX += cellWidth; }

date = [_calendar dateByAddingComponents:offsetComponents toDate:date options:0]负责让日期每天加 1,网格填满 42 格时,正好覆盖“上月末尾 + 本月全部 + 下月开头”,这也是为什么网格里总会出现浅灰色的邻月日期——它们不是错误,而是为了让网格始终填满 6 行。🧩

五、居中与对齐:坐标计算的数学细节

细心的读者会发现,截图里网格四周是有留白的,这正是源码里两行“居中公式”的功劳:

int curY = (areaHeight - cellHeight*_weeksOnCalendar)/2; // 垂直居中 int origX = (areaWidth - cellWidth*7)/2; // 水平居中

先用容器总尺寸减去网格总尺寸,再除以 2,得到的就是上下左右的边距。这样即使在不同屏幕宽度下,日历网格布局也能始终保持在容器正中央。

同样的偏移逻辑还被用在redrawWeekdays里:星期表头(Sun~Sat)用同一个dayWidth和相同的curX起点绘制,从而保证表头与下方每一列日期严格对齐。看这张横屏截图,网格和星期行的对齐关系一目了然:

六、单元格如何表达每一天的状态?

网格只是骨架,真正让日历“活”起来的是THDateDay这个单元格视图(源码位于THCalendarDatePicker/THDateDay.m)。它封装了每一天的多种视觉状态:

  • 选中态setSelected:把背景染成selectedBackgroundColor,文字变白;
  • 邻月淡化setLightText:让非本月日期显示为浅灰色,区分主次;
  • 今天高亮isToday比对年月日,用currentDateColor给今天的数字上色;
  • 禁用态setEnabled:配合dateInFutureAndShouldBeDisabled:限制历史/未来日期可选;
  • 事件标记indicateDayHasItems:控制日期下方的小圆点,可用于提示“这一天有行程/数据”。

如果开启了roundedaddMaskToBounds:还会用圆形遮罩把格子裁成圆角,观感更柔和。🎨

七、网格之外的交互:点击、滑动与翻页动画

网格铺好后,交互层的设计同样值得学习:

  • 点击选日THDateDay通过代理把点击事件上抛给dateDayTapped:,控制器再按单选、多选、区间三种模式(THDatePickerSelectionType)分别处理,选完还会回调给上层业务代码;
  • 滑动翻页:在calendarDaysView上注册了四个方向的滑动手势——左右滑切换年份、上下滑切换月份(关闭年份切换时左右滑变月份);
  • 翻页动画slideTransitionViewInDirection:会创建一个新网格视图,让新旧两个视图同时反向滑动、交叉淡入淡出,整个过渡只有约 0.5 秒;
  • 智能跳月:点击邻月的浅灰色日期时,日历会自动滑动到对应月份,并保持选中该日期,体验非常顺滑。

想快速体验这些效果,克隆仓库后打开Examples/THCalendarDatePickerExample下的示例工程即可,TestViewController.m里演示了完整的配置流程,包括设置日期范围、禁用历史选择、自定义选中色与圆点回调等。

小结

回顾整个 THCalendarDatePicker 日历网格布局算法,核心其实只有四步:用 7 列固定 6 行撑起 42 格画布 → 根据一号星期几算出起始偏移 → 从“本月 1 号减偏移”的日期开始逐格填充 → 用居中公式保证整体对齐。理解了这套思路,你不仅能看懂这个项目的源码,以后再见到任何自定义日历控件,都能快速抓住它的布局命脉。希望这篇源码解析能帮你彻底吃透日历网格布局算法!🚀

【免费下载链接】THCalendarDatePickerA DatePicker based on a custom calendar view项目地址: https://gitcode.com/gh_mirrors/th/THCalendarDatePicker

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/20 21:11:16

ZCode与DeepSeek集成指南:打造本地AI编程助手

在实际 AI 开发和学习过程中&#xff0c;我们经常需要将代码生成、代码解释、代码调试等能力集成到自己的开发环境或工作流中。DeepSeek 作为强大的 AI 模型&#xff0c;提供了优秀的代码理解和生成能力&#xff0c;而 ZCode 则是一个旨在连接 AI 模型与开发者本地环境的工具或…

作者头像 李华
网站建设 2026/8/20 21:07:21

Dtims CPB4612-LF 控制板

Dtims CPB4612-LF 控制板该控制板为Dtims&#xff08;Diversified Technology&#xff09;推出的6U CompactPCI规格单板计算机&#xff0c;基于Intel Pentium M处理器&#xff0c;适用于工业控制、通信设备及嵌入式系统等场景。产品特点采用Intel Pentium M处理器&#xff0c;集…

作者头像 李华