深入源码THCalendarDatePicker 日历网格布局算法是如何工作的【免费下载链接】THCalendarDatePickerA DatePicker based on a custom calendar view项目地址: https://gitcode.com/gh_mirrors/th/THCalendarDatePickerTHCalendarDatePicker 是一款基于自定义日历视图构建的 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就开始“铺格子”了整体流程可以概括为找起点从firstOfCurrentMonth往回退_bufferDaysBeginning天得到网格中第一个格子对应的日期循环 42 次每次创建一个THDateDay视图从THDateDay.xib加载放进网格并让它指向当前日期前进与换行每处理一格curX cellWidth当索引i % 7 0时回到行首curX origX同时curY cellHeight进入下一行写日期数字取出日期的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里星期表头SunSat用同一个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),仅供参考