1/* 2 * Copyright (c) 2024 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import CommonData from '../common/CommonData'; 17import Constants from '../constant/Constants'; 18import { OffscreenCanvas } from '../model/OffscreenCanvas'; // 离屏画布类 19import { TimeUtils } from '../utils/TimeUtils'; // 时间计算工具类 20 21/** 22 * 年视图子组件 23 */ 24@Component 25export struct YearViewItem { 26 // 年视图离屏画布列表 27 @State yearViewList: Array<OffscreenCanvas> = []; 28 @Prop @Watch('updateYearData') year: number; 29 // 年视图月份点击回调 30 onMonthClick: (year: number, month: number) => void = () => { 31 }; 32 33 /** 34 * 更新年数据 35 */ 36 updateYearData() { 37 this.yearViewList = []; 38 for (let i = 1; i <= Constants.MONTHS_NUM; i++) { 39 this.yearViewList.push(new OffscreenCanvas(this.year, i)); 40 } 41 } 42 43 aboutToAppear(): void { 44 // 添加年视图中每个月的离屏画布对象。一个画布对象绘制一个月数据。 45 for (let i = 1; i <= Constants.MONTHS_NUM; i++) { 46 this.yearViewList.push(new OffscreenCanvas(this.year, i)); 47 } 48 } 49 50 build() { 51 Grid() { 52 ForEach(this.yearViewList, (monthItem: OffscreenCanvas) => { 53 GridItem() { 54 // TODO: 高性能知识点: 年视图使用Canvas绘制显示年视图中每个月,以减少节点数量,同时使用OffscreenCanvasRenderingContext2D离 55 // 屏绘制,将需要绘制的内容先绘制在缓存区,然后将其转换成图片,一次性绘制到canvas上,以加快绘制速度。 56 Canvas(monthItem.context) 57 .width($r('app.string.calendar_switch_full_size')) 58 .height($r('app.string.calendar_switch_full_size')) 59 .onReady(() => { 60 // 绘制年视图中一个月的数据 61 let isTodayMoth: boolean = 62 monthItem.year === Constants.TODAY_YEAR && monthItem.month === Constants.TODAY_MONTH; 63 // 画月 64 monthItem.offContext.font = Constants.YEAR_VIEW_MONTH_FONT[CommonData.DEVICE_TYPE]; 65 monthItem.offContext.fillStyle = isTodayMoth ? Color.Red : Color.Black; 66 monthItem.offContext.fillText(Constants.MONTHS[monthItem.month-1], 67 Constants.YEAR_VIEW_INIT_THREE[CommonData.DEVICE_TYPE], 68 Constants.YEAR_VIEW_MONTH_HEIGHT[CommonData.DEVICE_TYPE]); 69 // 水平偏移 70 let horizontalOffset = Constants.YEAR_VIEW_INIT_THREE[CommonData.DEVICE_TYPE]; 71 // 画星期 72 monthItem.offContext.font = Constants.YEAR_VIEW_WEEK_FONT[CommonData.DEVICE_TYPE]; 73 for (let i = 0; i < Constants.WEEKS.length; i++) { 74 // 星期六,日字体颜色设置灰色 75 if (i === 0 || i === 6) { 76 monthItem.offContext.fillStyle = Constants.YEAR_VIEW_FONT_COLOR; 77 } 78 monthItem.offContext.fillText(Constants.WEEKS[i], horizontalOffset, 79 Constants.YEAR_VIEW_WEEK_HEIGHT[CommonData.DEVICE_TYPE]); 80 monthItem.offContext.fillStyle = Color.Black; 81 horizontalOffset += Constants.YEAR_VIEW_HORIZONTAL_OFFSET[CommonData.DEVICE_TYPE]; 82 } 83 // 获取月份日期前占位个数 84 const INTERVAL_COUNT: number = TimeUtils.getWeekDay(monthItem.year, monthItem.month, 1); 85 // 画日期 86 monthItem.offContext.font = Constants.YEAR_VIEW_DAY_FONT[CommonData.DEVICE_TYPE]; 87 monthItem.offContext.fillStyle = Color.Black; 88 // 获取每个月的总天数 89 const TOTAL_DAYS_IN_MONTH: number = TimeUtils.getMonthDays(monthItem.year, monthItem.month); 90 // 获取一个月占几周。向上取整 91 const WEEK_LENGTH = Math.ceil((INTERVAL_COUNT + TOTAL_DAYS_IN_MONTH) / 7); 92 // 初始化绘制日期。从1号开始绘制 93 let dayIndex = 1; 94 // 日期垂直偏移 95 let verticalOffset = Constants.YEAR_VIEW_DAY_HEIGHT[CommonData.DEVICE_TYPE]; 96 for (let i = 0; i < WEEK_LENGTH; i++) { 97 horizontalOffset = Constants.YEAR_VIEW_INIT_THREE[CommonData.DEVICE_TYPE]; 98 // 画一周 99 for (let j = 1; j <= Constants.DAYS_IN_WEEK; j++) { 100 if (i === 0 && j <= INTERVAL_COUNT) { 101 // 月份日期前占位 102 } else { 103 // 判断绘制的日期是不是今天。如果是今天,日期绘制圆圈红色背景,白色字体。如果不是今天,黑色字体 104 if (isTodayMoth && Constants.TODAY === dayIndex) { 105 106 // 画圆圈 107 monthItem.offContext.fillStyle = Color.Red; 108 // 绘制弧线路径。这里绘制圆圈。arc入参分别是弧线圆心的x坐标值,弧线圆心的y坐标值,弧线的圆半径,弧线的起始弧度,弧线的 109 // 终止弧度。5和3是圆圈x,y坐标绘制位置的微调值 110 monthItem.offContext.arc(horizontalOffset + 5 + 111 Constants.YEAR_VIEW_X_AXIS[CommonData.DEVICE_TYPE], 112 verticalOffset - 3 - Constants.YEAR_VIEW_Y_AXIS[CommonData.DEVICE_TYPE], 113 Constants.YEAR_VIEW_TODAY_RADIUS[CommonData.DEVICE_TYPE], Constants.DEFAULT, 114 Constants.YEAR_VIEW_TODAY_END_ANGLE[CommonData.DEVICE_TYPE]); 115 // 对封闭路径进行填充。 116 monthItem.offContext.fill(); 117 // 设置白色字体 118 monthItem.offContext.fillStyle = Color.White; 119 } else { 120 if (j === 1 || j === 7) { 121 // 星期日和星期六字体设置灰色 122 monthItem.offContext.fillStyle = Constants.YEAR_VIEW_FONT_COLOR; 123 } else { 124 monthItem.offContext.fillStyle = Color.Black; 125 } 126 } 127 // 画日期 128 if (dayIndex < 10) { 129 // 日期1-9号,字体水平位置微调3vp 130 monthItem.offContext.fillText(dayIndex.toString(), 3 + horizontalOffset, verticalOffset); 131 } else { 132 monthItem.offContext.fillText(dayIndex.toString(), horizontalOffset, verticalOffset); 133 } 134 // 画线。如果是农历初一,则日期底部加下划线 135 if (TimeUtils.isLunarFirstDayOfMonth(monthItem.year, monthItem.month, dayIndex)) { 136 monthItem.offContext.font = Constants.YEAR_VIEW_UNDERLINE[CommonData.DEVICE_TYPE]; 137 monthItem.offContext.fillStyle = Color.Red; 138 monthItem.offContext.fillText('_', 1 + horizontalOffset, verticalOffset); 139 monthItem.offContext.font = Constants.YEAR_VIEW_DAY_FONT[CommonData.DEVICE_TYPE]; 140 } 141 // 重置日期字体颜色 142 monthItem.offContext.fillStyle = Color.Black; 143 dayIndex++; 144 } 145 // 日期绘制水平偏移 146 horizontalOffset += Constants.YEAR_VIEW_HORIZONTAL_OFFSET[CommonData.DEVICE_TYPE]; 147 if (dayIndex > TOTAL_DAYS_IN_MONTH) { 148 break; 149 } 150 } 151 // 周绘制垂直偏移 152 verticalOffset += Constants.YEAR_VIEW_VERTICAL_OFFSET[CommonData.DEVICE_TYPE]; 153 } 154 // 从OffscreenCanvas组件中最近渲染的图像创建一个ImageBitmap对象 155 const IMAGE = monthItem.offContext.transferToImageBitmap(); 156 // 显示给定的ImageBitmap对象 157 monthItem.context.transferFromImageBitmap(IMAGE); 158 }) 159 } 160 .height($r('app.string.calendar_switch_size_twenty_three')) 161 .width($r('app.string.calendar_switch_size_twenty_five')) 162 .onClick(() => { 163 this.onMonthClick(monthItem.year, monthItem.month); 164 }) 165 }, (monthItem: OffscreenCanvas) => monthItem.year + '' + monthItem.month + '' + CommonData.DEVICE_TYPE) 166 } 167 .onSizeChange(() => { 168 if (CommonData.IS_FOLD) { 169 // 折叠屏展开态和折叠态尺寸变化时需要刷新当前年视图 170 this.updateYearData(); 171 } 172 }) 173 .scrollBar(BarState.Off) 174 .columnsTemplate('1fr 1fr 1fr') 175 .columnsGap($r('app.integer.calendar_switch_columns_gap')) 176 .rowsGap($r('app.integer.calendar_switch_rows_gap')) 177 } 178}