公司动态

HarmonyOs应用《重要日》开发第12篇 - 月视图日历组件详解

📅 2026/7/18 14:56:00
HarmonyOs应用《重要日》开发第12篇 - 月视图日历组件详解
本文深入解析 ImportantDays 项目中最复杂的 UI 组件MonthWeekCalendar包括其组件参数设计、月视图网格渲染、日期项的选中/今天/重要日标记逻辑、周视图渲染、无限滚动的联动处理以及日期文字颜色的多级优先策略。一、组件定位MonthWeekCalendar是日历模块的核心 UI 组件负责渲染月视图42 格网格和周视图7 天横排标记今天、选中日期在日期上显示重要日颜色圆点和标题预览处理日期点击事件联动 Swiper 无限滚动二、组件参数设计ComponentV2exportstruct MonthWeekCalendar{Paramvm:CalendarVMCalendarVM.getInstance();ParamonChangeDay:(date:Date)void(){};ParamselectedColor:string#FF6B6B;ParamstartWeekday:number0;ParamisShowHeader:booleantrue;privatemanager:ImportantDayManagerImportantDayManager.getInstance();privateswiperController:SwiperControllernewSwiperController();}参数说明参数类型默认值说明vmCalendarVM单例日历视图模型onChangeDay(date: Date) void空函数日期选择回调selectedColorstring#FF6B6B选中日期的高亮颜色startWeekdaynumber0一周起始日0周日isShowHeaderbooleantrue是否显示星期标题行设计考量vm 通过 Param 传入虽然 vm 是单例但通过Param传入使组件更灵活理论上可传入不同的 VM 实例回调函数模式onChangeDay让父组件决定如何处理日期选择manager 直接引用private manager直接获取单例因为不需要外部控制swiperController 私有Swiper 控制器是组件内部状态三、build 方法整体结构build(){Column(){if(this.isShowHeader){// 星期标题行Row(){ForEach(this.getWeekNames(),(name:string,index:number){Text(name).fontSize(14).fontColor(index0||index6?#FF6B6B:#666666).layoutWeight(1).textAlign(TextAlign.Center)})}.width(100%).padding({top:8,bottom:8})}// 日历主体 - Swiper LazyForEachSwiper(this.swiperController){LazyForEach(this.vm.monthWeekDataSource,(data:DateModelList){Column(){if(this.vm.viewTypeViewTypeEnum.MONTH){this.monthGrid(data)}else{this.weekRow(data)}}},(data:DateModelList,index:number)month_${index})}.cachedCount(CALENDAR_SWIPER_COUNT).index(this.vm.currentSwiperIndex).loop(false).indicator(false).onChange((index:number){this.handleSwiperChange(index);})}.width(100%)}结构层次Column ├── Row (星期标题行) — 条件显示 └── Swiper └── LazyForEach └── Column ├── monthGrid (月视图) — 条件渲染 └── weekRow (周视图) — 条件渲染四、星期标题行privategetWeekNames():string[]{constnames[...WEEK_NAMES];// [日, 一, 二, 三, 四, 五, 六]if(this.startWeekday1){names.push(names.shift()!);// 周一起始[一, 二, 三, 四, 五, 六, 日]}returnnames;}周日/周一起始处理周日起始默认[日, 一, 二, 三, 四, 五, 六]周一起始将 ‘日’ 移到末尾 →[一, 二, 三, 四, 五, 六, 日]周末颜色区分.fontColor(index0||index6?#FF6B6B:#666666)第一个和最后一个周末显示红色工作日显示灰色。五、月视图网格monthGridBuildermonthGrid(data:DateModelList){Grid(){ForEach(data,(dateModel:DateModel,index:number){GridItem(){this.monthDayItem(dateModel)}},(dateModel:DateModel,index:number)${dateModel.dateStr}_${index})}.columnsTemplate(1fr 1fr 1fr 1fr 1fr 1fr 1fr).rowsGap(4).width(100%)}网格配置7 列columnsTemplate(1fr 1fr 1fr 1fr 1fr 1fr 1fr)行间距rowsGap(4)— 4vp 的行间距数据42 个DateModel6行×7列键值${dateModel.dateStr}_${index}— 日期字符串 索引确保唯一六、日期项渲染monthDayItem这是最复杂的 Builder负责单个日期格的完整渲染。BuildermonthDayItem(dateModel:DateModel){Stack({alignContent:Alignment.Center}){// 1. 选中日期的背景圆if(this.vm.isDateSelect(dateModel.dayjsObj)){Circle({width:36,height:36}).fill(this.selectedColor)}// 2. 今天的高亮边框非选中时if(this.vm.isToday(dateModel.dayjsObj)!this.vm.isDateSelect(dateModel.dayjsObj)){Circle({width:36,height:36}).fill(#FFFFFF).stroke(this.selectedColor).strokeWidth(2)}// 3. 内容列Column(){// 日期数字Text(${dateModel.day}).fontSize(16).fontWeight(this.vm.isDateSelect(dateModel.dayjsObj)?FontWeight.Bold:FontWeight.Normal).fontColor(this.getDayTextColor(dateModel))// 重要日标记if(this.hasImportantDay(dateModel)){// 颜色圆点行最多3个Row(){ForEach(this.getImportantDays(dateModel).slice(0,3),(day:ImportantDay){Circle({width:5,height:5}).fill(day.color).margin({right:2})})}.height(6).margin({top:2})// 第一个重要日的标题预览Text(this.getFirstDayShortText(dateModel)).fontSize(8).fontColor(this.getFirstDayColor(dateModel)).maxLines(1).textOverflow({overflow:TextOverflow.Ellipsis}).width(34).textAlign(TextAlign.Center).margin({top:1})}}}.width(100%).height(52).onClick((){this.onDateClick(dateModel);})}渲染层次Stack (居中对齐) ├── Circle (选中背景) — 条件 ├── Circle (今天边框) — 条件 └── Column ├── Text (日期数字) └── Row (重要日标记) — 条件 ├── Row (颜色圆点 × 最多3个) └── Text (标题预览)选中与今天的视觉区分状态视觉表现选中日期实心圆背景 白色粗体数字今天未选中空心圆边框 红色数字今天已选中实心圆背景 白色粗体数字普通日期无装饰 正常颜色数字重要日标记设计颜色圆点最多显示 3 个圆点每个圆点使用重要日的自定义颜色标题预览显示第一个重要日的标题超出宽度时省略字体大小8vp在有限的格子空间内显示更多信息七、日期文字颜色策略privategetDayTextColor(dateModel:DateModel):string{if(this.vm.isDateSelect(dateModel.dayjsObj)){return#FFFFFF;// 选中 → 白色}if(!dateModel.isCurrentMonth){return#CCCCCC;// 非当月 → 灰色}if(this.vm.isToday(dateModel.dayjsObj)){returnthis.selectedColor;// 今天 → 主题色}if(this.hasImportantDay(dateModel)){returnthis.getFirstDayColor(dateModel);// 有重要日 → 重要日颜色}return#333333;// 普通 → 深灰}优先级链选中 → 非当月 → 今天 → 有重要日 → 普通优先级条件颜色1选中日期#FFFFFF白色2非当月#CCCCCC浅灰3今天selectedColor主题色4有重要日day.color重要日颜色5普通#333333深灰设计考量选中状态优先级最高白色文字配彩色背景最清晰非当月优先于今天/重要日避免非当月的今天造成混淆。八、周视图weekRowBuilderweekRow(data:DateModelList){Row(){ForEach(data,(dateModel:DateModel,index:number){Column(){Text(${dateModel.day}).fontSize(16).fontWeight(this.vm.isDateSelect(dateModel.dayjsObj)?FontWeight.Bold:FontWeight.Normal).fontColor(this.getDayTextColor(dateModel))if(this.hasImportantDay(dateModel)){Circle({width:5,height:5}).fill(this.getFirstDayColor(dateModel)).margin({top:2})}}.layoutWeight(1).height(56).justifyContent(FlexAlign.Center).onClick((){this.onDateClick(dateModel);})})}.width(100%)}与月视图的差异特性月视图周视图布局Grid 7列Row 等分选中背景Circle 填充无仅加粗文字重要日标记圆点 标题预览仅圆点高度52vp56vp周视图更简洁适合快速浏览一周概况。九、日期点击处理privateonDateClick(dateModel:DateModel):void{this.vm.changeDate(dateModel.dayjsObj);this.onChangeDay(dateModel.dayjsObj.toDate());}点击日期时更新 VM 中的选中日期触发外部回调通知父组件十、Swiper 切换联动privatehandleSwiperChange(index:number):void{this.vm.currentSwiperIndexindex;this.vm.handleHorizontalSwiper(index);constcurrentDatathis.vm.monthWeekDataSource.getData(index);if(currentDatacurrentData.length15){constmiddleDatecurrentData[15].dayjsObj;if(!this.vm.selectDate.isSame(middleDate,month)){this.vm.selectDatemiddleDate;}}}联动逻辑更新当前 Swiper 索引调用 VM 处理边界追加更新选中日期到当前月份取索引15的日期作为月份参考十一、重要日查询方法privatehasImportantDay(dateModel:DateModel):boolean{returnthis.manager.hasImportantDay(dateModel.dateStr);}privategetImportantDays(dateModel:DateModel):ImportantDay[]{returnthis.manager.getDaysByDate(dateModel.dateStr);}privategetFirstDayColor(dateModel:DateModel):string{constdaysthis.getImportantDays(dateModel);if(days.length0){returndays[0].color;}return#FF6B6B;}privategetFirstDayShortText(dateModel:DateModel):string{constdaysthis.getImportantDays(dateModel);if(days.length0){returndays[0].title;}return;}这些方法封装了对ImportantDayManager的查询提供组件级别的便捷访问。性能考量每次渲染日期项时都会查询dayMap但由于dayMap是Map结构查找复杂度为 O(1)42 个日期格的查询总开销可忽略。十二、总结MonthWeekCalendar是整个项目中 UI 逻辑最密集的组件双视图渲染月视图和周视图通过条件渲染切换多层视觉选中背景、今天边框、重要日标记层层叠加颜色优先级5 级颜色策略确保信息层次清晰事件联动点击和滑动事件与 VM 双向同步性能优化Map 查询 LazyForEach 保证渲染效率这个组件完美展示了 ArkUI 声明式 UI 在复杂交互场景下的表达能力。