行业资讯
HarmonyOs应用《重要日》开发第11篇 - 年视图组件
本文解析 ImportantDays 项目中年视图的两个组件YearCalendar负责整体年份网格布局和无限滚动BaseCalendar负责单月小日历的渲染和重要日标记。两个组件协作呈现完整的年视图日历体验。一、年视图整体设计年视图将一整年的 12 个月以小日历网格的形式展示用户可以快速浏览全年概况并通过颜色圆点识别哪些日期有重要日。组件协作关系CalendarPage └── YearCalendar (年视图容器) ├── 年份标题 └── Swiper LazyForEach └── Grid (3×4 月份网格) └── BaseCalendar × 12 (单月小日历)二、YearCalendar 组件组件定义ComponentV2exportstruct YearCalendar{Paramvm:CalendarVMCalendarVM.getInstance();ParamonSelectDate:(date:Date)void(){};LocalyearDataSource:YearDataSourcenewYearDataSource();LocalcurrentYear:numbernewDate().getFullYear();privateswiperController:SwiperControllernewSwiperController();}参数说明参数类型说明vmCalendarVM日历视图模型通过 Param 传入onSelectDate(date: Date) void日期选择回调yearDataSourceYearDataSource年份数据源Local 内部状态currentYearnumber当前显示年份swiperControllerSwiperControllerSwiper 控制器初始化aboutToAppear():void{this.yearDataSourcethis.vm.yearDataSource;this.currentYearthis.vm.selectDate.year();}在组件即将出现时从 VM 获取数据源和当前年份。build 方法build(){Column(){// 年份标题Row(){Text(${this.currentYear}年).fontSize(20).fontWeight(FontWeight.Bold).fontColor(#333333)}.width(100%).justifyContent(FlexAlign.Center).padding({top:8,bottom:12})// 月份网格 - Swiper LazyForEachSwiper(this.swiperController){LazyForEach(this.yearDataSource,(year:number){Grid(){ForEach([0,1,2,3,4,5,6,7,8,9,10,11],(month:number){GridItem(){BaseCalendar({year:year,month:month,startWeekday:this.vm.startWeekday})}},(month:number)${year}_${month})}.columnsTemplate(1fr 1fr 1fr).rowsGap(8).columnsGap(4).width(100%).height(100%).padding(4)},(year:number)year_${year})}.cachedCount(CALENDAR_SWIPER_COUNT).index(1).loop(false).indicator(false).onChange((index:number){this.handleYearSwiperChange(index);}).layoutWeight(1)}.width(100%).height(100%)}布局结构Column ├── Row (年份标题) └── Swiper └── LazyForEach └── Grid (3列) └── GridItem × 12 └── BaseCalendarGrid 配置3 列 4 行columnsTemplate(1fr 1fr 1fr)— 每行 3 个月共 4 行行间距rowsGap(8)— 月份之间的垂直间距列间距columnsGap(4)— 月份之间的水平间距月份网格布局┌────────┬────────┬────────┐ │ 1月 │ 2月 │ 3月 │ ├────────┼────────┼────────┤ │ 4月 │ 5月 │ 6月 │ ├────────┼────────┼────────┤ │ 7月 │ 8月 │ 9月 │ ├────────┼────────┼────────┤ │ 10月 │ 11月 │ 12月 │ └────────┴────────┴────────┘三、年视图无限滚动privatehandleYearSwiperChange(index:number):void{if(indexthis.yearDataSource.totalCount()-1){// 追加下一年constlastYearthis.yearDataSource.getData(this.yearDataSource.totalCount()-1);this.yearDataSource.addData(lastYear1);}elseif(index0){// 前插上一年constfirstYearthis.yearDataSource.getData(0);this.yearDataSource.addDataFromHead(firstYear-1);}constcurrentYearthis.yearDataSource.getData(index);this.currentYearcurrentYear;this.vm.selectDatethis.vm.selectDate.year(currentYear);}滚动逻辑滑到末尾追加下一年lastYear 1滑到开头前插上一年firstYear - 1更新状态同步currentYear和 VM 的selectDate滚动示例初始: [2025] [2026*] [2027] 滑到2027: [2025] [2026] [2027*] → 追加2028 → [2025] [2026] [2027*] [2028]四、BaseCalendar 组件BaseCalendar负责渲染单月的小日历是年视图中每个月份的独立单元。组件定义Componentexportstruct BaseCalendar{Propyear:number0;Propmonth:number0;PropstartWeekday:number0;privatevm:CalendarVMCalendarVM.getInstance();privatemanager:ImportantDayManagerImportantDayManager.getInstance();}注意使用 V1 装饰器BaseCalendar使用Component和PropV1而非ComponentV2和ParamV2。这可能是因为Prop在 V1 中是值拷贝适合简单类型参数小日历组件不需要响应式更新年视图切换时整体重渲染保持与可能存在的 V1 组件兼容build 方法build(){Column(){// 月份标题Text(${this.month1}月).fontSize(14).fontWeight(FontWeight.Medium).fontColor(#333333).margin({bottom:4})// 星期标题行Row(){ForEach(WEEK_NAMES,(name:string){Text(name).fontSize(10).fontColor(#999999).layoutWeight(1).textAlign(TextAlign.Center)})}.width(100%)// 日期网格Grid(){ForEach(this.getDateList(),(dateModel:DateModel,index:number){GridItem(){this.dayItem(dateModel)}},(dateModel:DateModel,index:number)${this.year}_${this.month}_${index})}.columnsTemplate(1fr 1fr 1fr 1fr 1fr 1fr 1fr).rowsGap(2).width(100%)}.width(100%).padding(8)}布局结构Column ├── Text (月份标题如 3月) ├── Row (星期标题行) └── Grid (7列) └── GridItem × 42 └── dayItem与 MonthWeekCalendar 的差异特性MonthWeekCalendarBaseCalendar装饰器ComponentV2Component参数传递Param(引用)Prop(值拷贝)字体大小16vp10-14vp日期项高度52vp22vp选中背景有Circle 填充无重要日标记圆点 标题预览仅圆点点击交互有无五、日期列表生成privategetDateList():DateModel[]{constdatedayjs().year(this.year).month(this.month);constlist:DateModel[][];constfirstDaydate.date(1);constdaysInMonthdate.daysInMonth();letfirstDayWeekday:numberfirstDay.day();firstDayWeekday(firstDayWeekday-this.startWeekdayWEEK_DAYS)%WEEK_DAYS;// 上月填充for(letifirstDayWeekday-1;i0;i--){constprevDatefirstDay.subtract(i1,day);list.push(newDateModel(prevDate,false));}// 当月for(leti1;idaysInMonth;i){constdayDatedate.date(i);list.push(newDateModel(dayDate,true));}// 下月填充while(list.length42){constlastDatelist[list.length-1].dayjsObj;list.push(newDateModel(lastDate.add(1,day),false));}returnlist;}此方法与CalendarVM.getDateList()逻辑相同但BaseCalendar独立实现以减少对 VM 的依赖。六、日期项渲染BuilderdayItem(dateModel:DateModel){Stack({alignContent:Alignment.Center}){// 重要日标记点if(this.hasImportantDay(dateModel)){Circle({width:4,height:4}).fill(this.getFirstDayColor(dateModel)).position({x:50%,y:2})}Text(${dateModel.day}).fontSize(11).fontColor(this.getDayColor(dateModel))}.width(100%).height(22)}标记点位置.position({x:50%,y:2})重要日标记圆点定位在日期格的顶部中央4×4vp 的小圆点不影响日期数字的显示。日期颜色策略privategetDayColor(dateModel:DateModel):string{if(!dateModel.isCurrentMonth){return#CCCCCC;// 非当月 → 灰色}if(this.vm.isToday(dateModel.dayjsObj)){return#FF6B6B;// 今天 → 红色}if(this.hasImportantDay(dateModel)){returnthis.getFirstDayColor(dateModel);// 有重要日 → 重要日颜色}return#333333;// 普通 → 深灰}颜色优先级非当月 → 今天 → 有重要日 → 普通注意年视图没有选中状态因为年视图主要用于浏览选中操作在月视图中进行。七、重要日查询privatehasImportantDay(dateModel:DateModel):boolean{returnthis.manager.hasImportantDay(dateModel.dateStr);}privategetFirstDayColor(dateModel:DateModel):string{constdaysthis.manager.getDaysByDate(dateModel.dateStr);if(days.length0){returndays[0].color;}return#FF6B6B;}与MonthWeekCalendar相同通过ImportantDayManager的dayMap查询。八、从年视图到月视图的切换在CalendarPage中年视图选择日期后会切换到月视图YearCalendar({vm:this.vm,onSelectDate:(date:Date){this.vm.changeDate(dayjs(date));this.vm.changeViewType(ViewTypeEnum.MONTH);this.updateTitle();this.updateSelectedDays();}})切换流程用户在年视图中点击某月 ↓ BaseCalendar 的小日历不响应点击 但 Grid 的整体可以扩展此功能 ↓ 实际上年视图通过 onSelectDate 回调 ↓ VM 切换到月视图 ↓ CalendarPage 重新渲染为月视图九、性能分析渲染开销年视图一次渲染 12 个月的小日历每个小日历有 42 个日期格总日期格数12 × 42 504 个每个日期格1 个 Text 可能 1 个 CircleDOM 节点数约 1000性能优化LazyForEach年视图的 Swiper 只渲染可见页预渲染页通过cachedCount控制小字体小尺寸10-11vp 字体、4vp 圆点减少渲染像素无交互小日历不响应点击减少事件处理开销Map 查询dayMap提供 O(1) 查询十、总结YearCalendar和BaseCalendar的协作展示了组件分层设计YearCalendar管理年份级别的 Swiper 滚动和 Grid 布局BaseCalendar专注于单月小日历的渲染和标记职责分离容器组件管布局和状态叶子组件管渲染性能可控LazyForEach 小尺寸渲染确保流畅V1/V2 混用展示了 ArkUI V1 和 V2 组件可以共存年视图为用户提供了全年重要日的鸟瞰视角是月视图的有力补充。
郑州网站建设
网页设计
企业官网