9afce6f6创建于 2025年5月7日历史提交
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { PlatformInfo, PlatformTypeEnum } from 'utils';
import Constants from '../constant/Constants';
import { OffscreenCanvas } from '../model/OffscreenCanvas'; // 离屏画布类
import { TimeUtils } from '../utils/TimeUtils'; // 时间计算工具类

/**
 * 年视图子组件
 */
@Component
export struct YearViewItem {
  // 年视图离屏画布列表
  @State yearViewList: Array<OffscreenCanvas> = [];
  @Prop @Watch('updateYearData') year: number;
  // 年视图月份点击回调
  onMonthClick: (year: number, month: number) => void = () => {
  };

  /**
   * 更新年数据
   */
  updateYearData() {
    this.yearViewList = [];
    for (let i = 1; i <= Constants.MONTHS_NUM; i++) {
      this.yearViewList.push(new OffscreenCanvas(this.year, i));
    }
  }

  aboutToAppear(): void {
    // 添加年视图中每个月的离屏画布对象。一个画布对象绘制一个月数据。
    for (let i = 1; i <= Constants.MONTHS_NUM; i++) {
      this.yearViewList.push(new OffscreenCanvas(this.year, i));
    }
  }

  build() {
    Grid() {
      ForEach(this.yearViewList, (monthItem: OffscreenCanvas) => {
        GridItem() {
          // TODO: 高性能知识点: 年视图使用Canvas绘制显示年视图中每个月,以减少节点数量,同时使用OffscreenCanvasRenderingContext2D离屏绘制,将需要绘制的内容先绘制在缓存区,然后将其转换成图片,一次性绘制到canvas上,以加快绘制速度。
          Canvas(monthItem.context)
            .width($r('app.string.calendar_switch_full_size'))
            .height($r('app.string.calendar_switch_full_size'))
            .onReady(() => {
              // 绘制年视图中一个月的数据
              let isTodayMoth: boolean =
                monthItem.year === Constants.TODAY_YEAR && monthItem.month === Constants.TODAY_MONTH;
              // 画月
              if (PlatformInfo.getPlatform() === PlatformTypeEnum.IOS) {
                monthItem.offContext.font = Constants.YEAR_VIEW_MONTH_FONT_FOR_IOS;
              } else {
                monthItem.offContext.font = Constants.YEAR_VIEW_MONTH_FONT;
              }
              monthItem.offContext.fillStyle = isTodayMoth ? Color.Red : Color.Black;
              monthItem.offContext.fillText(Constants.MONTHS[monthItem.month-1], Constants.YEAR_VIEW_INIT_THREE,
                Constants.YEAR_VIEW_MONTH_HEIGHT);
              // 水平偏移
              let horizontalOffset = Constants.YEAR_VIEW_INIT_THREE;
              // 画星期
              if (PlatformInfo.getPlatform() === PlatformTypeEnum.IOS) {
                monthItem.offContext.font = Constants.YEAR_VIEW_WEEK_FONT_FOR_IOS;
              } else {
                monthItem.offContext.font = Constants.YEAR_VIEW_WEEK_FONT;
              }
              for (let i = 0; i < Constants.WEEKS.length; i++) {
                // 星期六,日字体颜色设置灰色
                if (i === 0 || i === 6) {
                  monthItem.offContext.fillStyle = Constants.YEAR_VIEW_FONT_COLOR;
                }
                monthItem.offContext.fillText(Constants.WEEKS[i], horizontalOffset, Constants.YEAR_VIEW_WEEK_HEIGHT);
                monthItem.offContext.fillStyle = Color.Black;
                horizontalOffset += Constants.YEAR_VIEW_HORIZONTAL_OFFSET;
              }
              // 获取月份日期前占位个数
              const INTERVAL_COUNT: number = TimeUtils.getWeekDay(monthItem.year, monthItem.month, 1);
              // 画日期
              if (PlatformInfo.getPlatform() === PlatformTypeEnum.IOS) {
                monthItem.offContext.font = Constants.YEAR_VIEW_DAY_FONT_FOR_IOS;
              } else {
                monthItem.offContext.font = Constants.YEAR_VIEW_DAY_FONT;
              }
              monthItem.offContext.fillStyle = Color.Black;
              // 获取每个月的总天数
              const TOTAL_DAYS_IN_MONTH: number = TimeUtils.getMonthDays(monthItem.year, monthItem.month);
              // 获取一个月占几周。向上取整
              const WEEK_LENGTH = Math.ceil((INTERVAL_COUNT + TOTAL_DAYS_IN_MONTH) / 7);
              // 初始化绘制日期。从1号开始绘制
              let dayIndex = 1;
              // 日期垂直偏移
              let verticalOffset = Constants.YEAR_VIEW_DAY_HEIGHT;
              for (let i = 0; i < WEEK_LENGTH; i++) {
                horizontalOffset = Constants.YEAR_VIEW_INIT_THREE;
                // 画一周
                for (let j = 1; j <= Constants.DAYS_IN_WEEK; j++) {
                  if (i === 0 && j <= INTERVAL_COUNT) {
                    // 月份日期前占位
                  } else {
                    // 判断绘制的日期是不是今天。如果是今天,日期绘制圆圈红色背景,白色字体。如果不是今天,黑色字体
                    if (isTodayMoth && Constants.TODAY === dayIndex) {
                      // 画圆圈
                      monthItem.offContext.fillStyle = Color.Red;
                      // 绘制弧线路径。这里绘制圆圈。arc入参分别是弧线圆心的x坐标值,弧线圆心的y坐标值,弧线的圆半径,弧线的起始弧度,弧线的终止弧度。5和3是圆圈x,y坐标绘制位置的微调值
                      monthItem.offContext.arc(horizontalOffset + 5, verticalOffset - 3,
                        Constants.YEAR_VIEW_TODAY_RADIUS, Constants.DEFAULT, Constants.YEAR_VIEW_TODAY_END_ANGLE);
                      // 对封闭路径进行填充。
                      monthItem.offContext.fill();
                      // 设置白色字体
                      monthItem.offContext.fillStyle = Color.White;
                    } else {
                      if (j === 1 || j === 7) {
                        // 星期日和星期六字体设置灰色
                        monthItem.offContext.fillStyle = Constants.YEAR_VIEW_FONT_COLOR;
                      } else {
                        monthItem.offContext.fillStyle = Color.Black;
                      }
                    }
                    // 画日期
                    if (dayIndex < 10) {
                      // 日期1-9号,字体水平位置微调3vp
                      monthItem.offContext.fillText(dayIndex.toString(), 3 + horizontalOffset, verticalOffset);
                    } else {
                      monthItem.offContext.fillText(dayIndex.toString(), horizontalOffset, verticalOffset);
                    }
                    // 画线。如果是农历初一,则日期底部加下划线
                    if (TimeUtils.isLunarFirstDayOfMonth(monthItem.year, monthItem.month, dayIndex)) {
                      if (PlatformInfo.getPlatform() === PlatformTypeEnum.IOS) {
                        monthItem.offContext.font = Constants.YEAR_VIEW_UNDERLINE_FOR_IOS;
                      } else {
                        monthItem.offContext.font = Constants.YEAR_VIEW_UNDERLINE;
                      }
                      monthItem.offContext.fillStyle = Color.Red;
                      monthItem.offContext.fillText('_', 1 + horizontalOffset, verticalOffset);
                      if (PlatformInfo.getPlatform() === PlatformTypeEnum.IOS) {
                        monthItem.offContext.font = Constants.YEAR_VIEW_DAY_FONT_FOR_IOS;
                      } else {
                        monthItem.offContext.font = Constants.YEAR_VIEW_DAY_FONT;
                      }
                    }
                    // 重置日期字体颜色
                    monthItem.offContext.fillStyle = Color.Black;
                    dayIndex++;
                  }
                  // 日期绘制水平偏移
                  horizontalOffset += Constants.YEAR_VIEW_HORIZONTAL_OFFSET;
                  if (dayIndex > TOTAL_DAYS_IN_MONTH) {
                    break;
                  }
                }
                // 周绘制垂直偏移
                verticalOffset += Constants.YEAR_VIEW_VERTICAL_OFFSET;
              }
              // 从OffscreenCanvas组件中最近渲染的图像创建一个ImageBitmap对象
              const IMAGE = monthItem.offContext.transferToImageBitmap();
              // 显示给定的ImageBitmap对象
              monthItem.context.transferFromImageBitmap(IMAGE);
            })
        }
        .id('month' + monthItem.month)
        .height($r('app.string.calendar_switch_size_twenty_three'))
        .width($r('app.string.calendar_switch_size_twenty_five'))
        .onClick(() => {
          this.onMonthClick(monthItem.year, monthItem.month);
        })
      }, (monthItem: OffscreenCanvas) => monthItem.year + '' + monthItem.month)
    }
    .id(this.year.toString())
    .scrollBar(BarState.Off)
    .columnsTemplate('1fr 1fr 1fr')
    .columnsGap($r('app.integer.calendar_switch_columns_gap'))
    .rowsGap($r('app.integer.calendar_switch_rows_gap'))
  }
}