#HarmonyOS NEXT体验官#梅科尔工作室HOS-自定义日历选择器 原创

梅科尔工作室HOS
发布于 2024-8-21 13:56
浏览
0收藏

作者:梅科尔夏一涵

目的

本示例介绍通过CustomDialogController类显示自定义日历选择器,在需要用户预约时间或安排事件的系统中,自定义日历选择器能让用户直观、方便地选择具体的日期和时间。同时再查看历史足迹等历史记录时更加方便的进行选择

效果预览

#HarmonyOS NEXT体验官#梅科尔工作室HOS-自定义日历选择器-鸿蒙开发者社区

实现过程

一、准备工作

创建一个新的空项目,新增目录结构 ets/components/ ,在下面添加文件添加DataModel.ets用于构建日期类,添加GetDate.ets用于获取日期,添加MonthDataSource.ets用于处理月份数据,添加DataManager.ets用于处理日期数据的持久化存储,在ets/pages/下添加CustomCalendarPikerDialog.ets用于构建自定义弹窗

二、获取本月以及下个月的日期信息

getMonthDate 函数根据指定的年份和月份,生成一个包含该月份在日历上排布的日期数组。数组中的每个元素表示该月日历上的一天,如果某天是上个月或下个月的(即,位于月份开始前的空白部分或月份结束后的空白部分),则相应位置上的值为0。


getRealTimeDate 函数获取当前日期。

export function getRealTimeDate(): DateModel{
  const nowDate = new Date(); // 创建Date对象,设置当前日期和时间
  let currentMonth = nowDate.getMonth() + 1; // 获取当前月份,getMonth()获得的值是0~11,实际月份需要+1
  let currentDay = nowDate.getDate(); // 获取当前日
  let currentYear = nowDate.getFullYear(); // 获取当前年份
  let currentWeekDay = new Date(currentYear, currentMonth - 1, currentDay).getDay(); // 获取当前星期几
  let nowDateModel = new DateModel(0, 0, 0, 0); // 创建DateModel实例
  nowDateModel.day = currentDay;
  nowDateModel.week = currentWeekDay;
  nowDateModel.month = currentMonth;
  nowDateModel.year = currentYear;
  return nowDateModel;
}

三、设置自定义日历选择器界面,初始化自定义日历弹窗

自定义日历选择器对话框组件提供一个用户友好的日历选择界面,允许用户选择日期。结合网格布局等循环渲染自定义一个日历选择器,可以根据项目的整体风格对日历对话框进行定制。

@CustomDialog
export struct CustomCalendarPickerDialog {
  ...
  controller: CustomDialogController; // 通过CustomDialogController类显示自定义弹窗
  cancel: () => void = () => {
  }; //点击遮障层退出时的回调
  private week: string[] = [SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY]; // 设置日历周,从周日开始
  @State customY: number = 0;
  readyToClose() {
    ...
  }

  // 获取当前月和下个月的日期数据
  aboutToAppear() {
    this.currentMonthDay = getMonthDate(this.currentMonth, this.currentYear);
    // 如果下个月是在下一年,则下个月是1月份,年份要+1
    if (this.currentMonth === MONTHS) {
      this.nextMonth = JANUARY;
      this.nextYear = this.currentYear + 1;
    }
    // 如果下个月是还是当前年,则月份+1,年份不变
    else {
      this.nextMonth = this.currentMonth + 1;
      this.nextYear = this.currentYear;
    }
    this.nextMonthDay = getMonthDate(this.nextMonth, this.nextYear);
    // 获取当前月和下个月的日期数据
    let months: Month[] = [
      {
        month: `${this.currentYear}年 ${this.currentMonth}月`,
        num: this.currentMonth,
        days: this.currentMonthDay
      },
      {
        month: `${this.nextYear}年 ${this.nextMonth}月`,
        num: this.nextMonth,
        days: this.nextMonthDay
      }
    ]
    this.contentData.pushData(months);
    this.initialIndex = this.dateModel.month > this.currentMonth ? 1 : 0; // 设置List初次加载时视口起始位置显示的item的索引值
  }
  ...
  // 自定义日历选取器内容
  build() {
    ...
      List() {
        // 此处日期固定,使用了ForEach,其他列表数量较多的场景,推荐LazyForEach+组件复用+缓存列表项实现
        ForEach(this.week, (weekInformation: string) => {
          ListItem() {
            ...
        })
      }
      ...
      // 每个月的日期
      List({ initialIndex: this.initialIndex }) {
        LazyForEach(this.contentData, (monthItem: Month) => {
          // 设置ListItemGroup头部组件,显示年份和月份
          ListItemGroup({ header: this.itemHead(monthItem.month) }) {
          ...
          }
          ...
          .onClick(() => {
            if (day >= this.currentDay || monthItem.num > this.currentMonth) {
              let weekIndex = monthItem.days.indexOf(day) % WEEK_NUMBER; // 将当前日转换成星期显示
              this.dateModel.day = day;
              this.dateModel.week = weekIndex;
              this.dateModel.month = monthItem.num;
              DataManager.setDate(getContext(this), this.dateModel, () => {
              });
              ...
                  })
                })
              }
              .backgroundColor($r('app.color.ohos_id_color_background'))
              .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
              // 当前月显示的数组元素个数大于35则显示6行,否则显示5行
              .rowsTemplate(monthItem.days.length > MONTH_NUMBER ? '1fr 1fr 1fr 1fr 1fr 1fr' : '1fr 1fr 1fr 1fr 1fr')
              .height(monthItem.days.length > MONTH_NUMBER ? GRID_HEIGHT_L : GRID_HEIGHT_M)
            }
          }
        })
      }
      ...
    }
   ...
}

四、通过用户首选项实现选择的日期数据持久化

定义DataManager类,提供了与用户首选项相关的数据持久化操作,特别是针对日期数据的获取、设置和删除功能。异步操作,并通过回调函数来通知调用者操作的结果。主要使用了 HarmonyOS 的 dataPreferences API 来实现数据的持久化存储。

首先看DataManager类中的getDate方法,从用户首选项中尝试获取日期数据selectedDate。如果找到,则将其解析为DateModel对象并通过回调函数返回;如果没有找到或发生错误,则通过getRealTimeDate函数获取。使用 dataPreferences.getPreferences(context, ‘date’) 获取名为 ‘date’ 的 Preferences 实例。
调用object.get(‘selectedDate’, ‘’) 尝试从 Preferences 实例中获取 ‘selectedDate’ 键对应的值。
如果获取到的字符串非空,则尝试使用 JSON.parse 将其解析为 DateModel 对象。
如果解析成功或失败(或未找到数据),都通过回调函数返回 dateModel。

static getDate(context: Context, callback: (dateModel: DateModel) => void) {
  let dateModel: DateModel = getRealTimeDate();
  try {
    // TODO: 知识点: 通过用户首选项实现数据持久化
    let promise = dataPreferences.getPreferences(context, 'date')
    promise.then((object: dataPreferences.Preferences) => {
      try {
        // 从缓存的Preferences实例中获取名为'selectedDate'键对应的值
        let getPromise = object.get('selectedDate', '')
        getPromise.then((data: dataPreferences.ValueType) => {
          let dateSting = data as string;
          if (dateSting.length > 0) {
            dateModel = JSON.parse(dateSting) as DateModel;
            callback(dateModel)
          } else {
            callback(dateModel)
          }
        }).catch((err: Error) => {
          callback(dateModel)
          console.error("LoginMessage: Failed to get value of selectedDate" + err.name + ", message = " + err.message)
        })
      } catch (err) {
        callback(dateModel)
        console.error("LoginMessage: Failed to get value of selectedDate" + err.name + ", message = " + err.message)
      }
    }).catch((err: Error) => {
      callback(dateModel)
      console.error("LoginMessage: Failed to get the preferences" + err.name + ", message = " + err.message)
    })
  } catch (err) {
    callback(dateModel)
    console.error("LoginMessage: Failed to get the preferences" + err.name + ", message = " + err.message)
  }
}

将指定的DateModel对象存储到用户首选项中的selectedDate键下,同样使用 dataPreferences.getPreferences(context, ‘date’) 获取 Preferences 实例。
使用object.put(‘selectedDate’, JSON.stringify(dateModel)) 将 DateModel 对象序列化为字符串并存储。
调用 object.flush() 将更改异步写入持久化文件。
尝试使用 AppStorage.setOrCreate<DateModel>(‘selectedDate’, dateModel) 创建一个键值对。

static setDate(context: Context, dateModel: DateModel, callback: () => void) {
  try {
    // 获取Preferences实例
    let promise = dataPreferences.getPreferences(context, 'date')
    promise.then((object: dataPreferences.Preferences) => {
      try {
        // 将数据写入缓存的Preferences实例中
        let setPromise = object.put('selectedDate', JSON.stringify(dateModel))
        setPromise.then(() => {
          // 将缓存的Preferences实例中的数据异步存储到用户首选项的持久化文件中
          let flushPromise = object.flush()
          flushPromise.then(() => {
            // 创建单个AppStorage的键值对
            AppStorage.setOrCreate<DateModel>('selectedDate', dateModel)
            callback()
          }).catch((err: Error) => {
            callback()
            console.error("LoginMessage: Failed to flush" + err.name + ", message = " + err.message)
          })
        }).catch((err: Error) => {
          callback()
          console.error("LoginMessage: Failed to put selectedDate" + err.name + ", message = " + err.message)
        })
      } catch (err) {
        callback()
        console.error("LoginMessage: Failed to put selectedDate" + err.name + ", message = " + err.message)
      }
    }).catch((err: Error) => {
      callback()
      console.error("LoginMessage: Failed to get preferences" + err.name + ", message = " + err.message)
    })
  } catch (err) {
    callback()
    console.error("LoginMessage: Failed to get preferences" + err.name + ", message = " + err.message)
  }
}

static deleteDate(context: Context, callback: () => void) {
  try {
    let promise = dataPreferences.getPreferences(context, 'date')
    promise.then((object: dataPreferences.Preferences) => {
      try {
        // 从缓存的Preferences实例中删除名为selectedDate的存储键值对
        let deletePromise = object.delete('selectedDate')
        deletePromise.then(() => {
          // 将缓存的Preferences实例中的数据异步存储到用户首选项的持久化文件中
          let flushPromise = object.flush()
          flushPromise.then(() => {
            // 创建单个AppStorage的键值对,key为selectedDate的键值对置为undefined
            AppStorage.setOrCreate<DateModel>('selectedDate', undefined)
            callback()
          }).catch((err: Error) => {
            console.error("LoginMessage: Failed to flush" + err.name + ", message = " + err.message)
          })
        }).catch((err: Error) => {
          console.error("LoginMessage: Failed to delete selectedDate" + err.name + ", message = " + err.message)
        })
      } catch (err) {
        console.error("LoginMessage: Failed to delete selectedDate" + err.name + ", message = " + err.message)
      }
    }).catch((err: Error) => {
      console.error("LoginMessage: Failed to get preferences" + err.name + ", message = " + err.message)
    })
  } catch (err) {
    console.error("LoginMessage: Failed to get preferences" + err.name + ", message = " + err.message)
  }
}

总结

关于在华为鸿蒙系统实现一个自定义日历选择器,本文主要是提供一些思路。开发者可以根据项目的具体需求,对日历选择器的样式、功能等进行高度定制,以匹配应用的整体风格和用户的使用习惯。
自定义日历选择器通常支持多种日历类型(如公历、农历等)、日期范围选择、可用性检查等功能,满足不同场景下的需求。随着HarmonyOS的不断演进和生态的日益丰富,未来的自定义日历选择器将融合更多创新技术与设计理念。HarmonyOS Next将是一个更加智能、开放、高效和创新的操作系统。它将不断推动技术的发展和生态的繁荣,为用户带来更加便捷、丰富和个性化的数字生活体验。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-8-21 13:58:30修改
1
收藏
回复
举报
回复
    相关推荐