#HarmonyOS NEXT体验官#梅科尔工作室HOS-自定义日历选择器 原创
作者:梅科尔夏一涵
目的
本示例介绍通过CustomDialogController类显示自定义日历选择器,在需要用户预约时间或安排事件的系统中,自定义日历选择器能让用户直观、方便地选择具体的日期和时间。同时再查看历史足迹等历史记录时更加方便的进行选择
效果预览
实现过程
一、准备工作
创建一个新的空项目,新增目录结构 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将是一个更加智能、开放、高效和创新的操作系统。它将不断推动技术的发展和生态的繁荣,为用户带来更加便捷、丰富和个性化的数字生活体验。