本示例介绍使用Swiper实现自定义日历年视图、月视图、周视图左右滑动切换年、月、周的效果。同时使用Tabs实现年视图、月视图、周视图之间的切换效果。还有使用Calendar Kit日历服务实现日程提醒的功能。
使用说明
日历切换功能:
// YearViewItem.ets
// 年视图
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(() => {
// 绘制年视图中一个月的数据
// ...
// 画月
monthItem.offContext.fillText(Constants.MONTHS[monthItem.month-1], Constants.YEAR_VIEW_INIT_THREE, Constants.YEAR_VIEW_MONTH_HEIGHT);
// 画星期
monthItem.offContext.fillText(Constants.WEEKS[i], horizontalOffset, Constants.YEAR_VIEW_WEEK_HEIGHT);
// 画日期
monthItem.offContext.fillText(dayIndex.toString(), horizontalOffset, verticalOffset);
// ...
// 从OffscreenCanvas组件中最近渲染的图像创建一个ImageBitmap对象
const IMAGE = monthItem.offContext.transferToImageBitmap();
// 显示给定的ImageBitmap对象
monthItem.context.transferFromImageBitmap(IMAGE);
})
}
}, (monthItem: OffscreenCanvas) => monthItem.year + '' + monthItem.month)
}
// MonthViewItem.ets
// 月视图
Column() {
ForEach(this.monthDays, (items: Day[], index: number) => {
Row() {
ForEach(items, (item: Day) => {
this.monthDayBuilder(item, index + 1)
}, (item: Day, index: number) => {
return item.dayNum + '' + index
})
}
.width($r('app.string.calendar_switch_full_size'))
.justifyContent(FlexAlign.SpaceBetween)
}, (item: Day[], index: number) => {
return item.reduce((item1, item2) => {
return item1 + item2.dayInfo.year + item2.dayInfo.month + item2.dayInfo.date
}, '') + index
})
}.width($r('app.string.calendar_switch_full_size'))
// WeekViewItem.ets
// 周视图
Column() {
ForEach(this.weekDays, (items: Day[]) => {
Row() {
ForEach(items, (item: Day) => {
this.weekDayBuilder(item)
}, (item: Day, index: number) => {
return item.dayNum + '' + index;
})
}
.width($r('app.string.calendar_switch_full_size'))
.justifyContent(FlexAlign.SpaceBetween)
}, (item: Day[], index: number) => {
return item.reduce((item1, item2) => {
return item1 + item2.dayInfo.year + item2.dayInfo.month + item2.dayInfo.date
}, '') + index
})
}.width($r('app.string.calendar_switch_full_size'))
Swiper() {
// 月视图子组件
MonthViewItem({
yearMonth: this.lastYearMonth,
currentSelectDate: this.currentSelectDate,
onDateClick: (year: number, month: number, date: number) => {
this.onDateClick(year, month, date);
},
CalendarStyle: {
textScaling: this.CalendarStyle.textScaling,
backgroundColor: this.CalendarStyle.backgroundColor,
monthDayColor: this.CalendarStyle.monthDayColor,
noMonthDayColor: this.CalendarStyle.noMonthDayColor,
lunarColor: this.CalendarStyle.lunarColor
}
})
MonthViewItem({
yearMonth: this.currentYearMonth,
currentSelectDate: this.currentSelectDate,
onDateClick: (year: number, month: number, date: number) => {
this.onDateClick(year, month, date);
},
CalendarStyle: {
textScaling: this.CalendarStyle.textScaling,
backgroundColor: this.CalendarStyle.backgroundColor,
monthDayColor: this.CalendarStyle.monthDayColor,
noMonthDayColor: this.CalendarStyle.noMonthDayColor,
lunarColor: this.CalendarStyle.lunarColor
}
})
MonthViewItem({
yearMonth: this.nextYearMonth,
currentSelectDate: this.currentSelectDate,
onDateClick: (year: number, month: number, date: number) => {
this.onDateClick(year, month, date);
},
CalendarStyle: {
textScaling: this.CalendarStyle.textScaling,
backgroundColor: this.CalendarStyle.backgroundColor,
monthDayColor: this.CalendarStyle.monthDayColor,
noMonthDayColor: this.CalendarStyle.noMonthDayColor,
lunarColor: this.CalendarStyle.lunarColor
}
})
}
.onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => {
if (this.oldMonthViewIndex === targetIndex) {
// 如果手指滑动swiper松开时,targetIndex和之前记录子组件索引oldMonthViewIndex一样,说明swiper没有切换子组件,不需要切换月份
return;
}
// 记录子组件索引
this.oldMonthViewIndex = targetIndex;
// 判断是否右滑切换月份
const IS_RIGHT_SLIDE: boolean = (index === 1 && targetIndex === 0) || (index === 0 && targetIndex === 2) ||
(index === 2 && targetIndex === 1);
// TODO: 高性能知识点: 左右滑动切换月时,每次切换月只更新一个月的数据,以优化月视图左右切换时的性能。年视图和周视图也类似,这里不再赘述。
// 右滑切换到上个月
if (IS_RIGHT_SLIDE) {
// 将当前月份设置为上个月
this.currentShowYear = TimeUtils.getLastYear(this.currentShowYear, this.currentShowMonth);
this.currentShowMonth = TimeUtils.getLastMonth(this.currentShowYear, this.currentShowMonth);
this.onChangeYearMonth(this.currentShowYear, this.currentShowMonth);
if (targetIndex === 0) {
// swiper索引右滑到0时,修改swiper索引2的月份为当前月份(索引0)的上一个月。比如,假设swiper索引0(7月),swiper索引1(8月),swiper索引2(9月)。当右滑切换到索引0(7月)时,需要把索引2(9月)的月份改成6月。
// 修改swiper索引2的月份为当前月份(索引0)的上一个月
this.nextYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth);
} else if (targetIndex === 1) {
// swiper索引右滑到1时,修改swiper索引0的月份为当前月份(索引1)的上一个月。
this.lastYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth);
} else if (targetIndex === 2) {
// swiper索引右滑到2时,修改swiper索引1的月份为当前月份(索引2)的上一个月。
this.currentYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth);
}
} else {
// 右滑切换到下个月
// 将当前月份设置为下个月
this.currentShowYear = TimeUtils.getNextYear(this.currentShowYear, this.currentShowMonth);
this.currentShowMonth = TimeUtils.getNextMonth(this.currentShowYear, this.currentShowMonth);
this.onChangeYearMonth(this.currentShowYear, this.currentShowMonth);
if (targetIndex === 0) {
// swiper索引左滑到0时,修改swiper索引1的月份为当前月份(索引0)的下一个月。
this.currentYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth);
} else if (targetIndex === 1) {
// swiper索引左滑到1时,修改swiper索引2的月份为当前月份(索引1)的下一个月。
this.nextYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth);
} else if (targetIndex === 2) {
// swiper索引左滑到2时,修改swiper索引0的月份为当前月份(索引2)的下一个月。
this.lastYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth);
}
}
})
// CustomCalendarSample.ets
this.tabController.changeIndex(index);
if (this.tabSelectedIndex === CalendarViewType.MONTH) {
this.currentShowYear = this.currentSelectDay.year;
this.currentShowMonth = this.currentSelectDay.month;
// 刷新月视图
this.calendarMonthController.swiperRefresh(CalendarViewType.MONTH);
} else if (this.tabSelectedIndex === CalendarViewType.WEEK) {
this.currentShowYear = this.currentSelectDay.year;
this.currentShowMonth = this.currentSelectDay.month;
// 刷新周视图
this.calendarWeekController.swiperRefresh(CalendarViewType.WEEK);
} else if (this.tabSelectedIndex === CalendarViewType.YEAR) {
// 刷新年视图
this.calendarYearController.swiperRefresh(CalendarViewType.YEAR);
}
// ...
onMonthClick: (year: number, month: number) => {
if (this.tabController) {
// 切到月视图
this.tabController.changeIndex(1);
// 刷新年月信息标题
this.currentShowYear = year;
this.currentShowMonth = month;
// 刷新在年视图上点击月后要跳转的月视图数据
this.calendarMonthController.swiperYearToMonthRefresh(year, month);
}
}
// MonthViewItem.ets
/**
* 获取指定月份数据
*/
getMonthViewData(year: number, month: number) {
this.monthDays = [...TimeUtils.byMonthDayForYear(year, month)];
}
// WeekViewItem.ets
/**
* 获取指定周数据
*/
getWeekViewData(weekNum: number) {
this.weekDays = [...TimeUtils.getWeekDays(weekNum)];
}
// YearViewItem.ets
/**
* 更新年数据
*/
updateYearData() {
this.yearViewList = [];
for (let i = 1; i <= Constants.MONTHS_NUM; i++) {
this.yearViewList.push(new OffscreenCanvas(this.year, i));
}
}
日程提醒功能:
前置条件:需要在module.json5中配置日历读写权限"ohos.permission.READ_CALENDAR"、"ohos.permission.WRITE_CALENDAR"。
const permissions: Permissions[] = ['ohos.permission.READ_CALENDAR', 'ohos.permission.WRITE_CALENDAR'];
// 获取AtManager实例
let atManager = abilityAccessCtrl.createAtManager();
// 向用户申请系统日历读写权限
atManager.requestPermissionsFromUser(this.context, permissions).then((result: PermissionRequestResult) => {
logger.info(TAG, `get Permission success, result: ${JSON.stringify(result)}`);
// 根据上下文获取CalendarManager对象,用于管理日历。
this.calendarMgr = calendarManager.getCalendarManager(this.context);
// 获取Calendar对象
this.calendarMgr.getCalendar(this.myCalendarAccount).then((data: calendarManager.Calendar) => {
this.calendar = data;
// 设置日历配置信息
this.calendar.setConfig(this.config).then(() => {
logger.info(TAG, `Succeeded in setting config, data->${JSON.stringify(this.config)}`);
}).catch((err: BusinessError) => {
logger.error(TAG, `Failed to set config. Code: ${err.code}, message: ${err.message}`);
});
}).catch(() => {
// 如果日历账户不存在,则创建日历账户
this.calendarMgr?.createCalendar(this.myCalendarAccount).then((data: calendarManager.Calendar) => {
// 请确保日历账户创建成功后,再进行后续相关操作
this.calendar = data;
// 设置日历账户
this.calendar?.setConfig(this.config).then(() => {
logger.info(TAG, `Succeeded in setting config, data->${JSON.stringify(this.config)}`);
}).catch((err: BusinessError) => {
logger.error(TAG, `Failed to set config. Code: ${err.code}, message: ${err.message}`);
});
}).catch((error: BusinessError) => {
logger.error(TAG, `Failed to create calendar. Code: ${error.code}, message: ${error.message}`);
});
});
}).catch((error: BusinessError) => {
logger.error(TAG, `get Permission error, error. Code: ${error.code}, message: ${error.message}`);
})
// 配置日程参数
const EVENT_NOT_REPEATED: calendarManager.Event = {
// 日程标题
title: this.title,
// 地点
location: { location: this.location },
// 日程类型,NORMAL:普通日程,例如会议,闹钟等日常提醒的日程。 IMPORTANT:重要日程,例如结婚纪念日等具有重要意义的日期,不推荐三方开发者使用,重要日程类型不支持一键服务跳转功能及无法自定义提醒时间。
type: calendarManager.EventType.NORMAL,
// 日程开始时间,需要13位时间戳。
startTime: this.startTime.getTime(),
// 日程结束时间,需要13位时间戳。
endTime: this.endTime.getTime(),
// 日程提醒时间,单位为分钟。填写x分钟,即距开始时间提前x分钟提醒,不填时,默认为不提醒。为负值时表示延期多长时间提醒。
reminderTime: this.reminderTimeArray
};
// 创建日程
this.calendar?.addEvent(EVENT_NOT_REPEATED).then((data: number) => {
logger.info(TAG, `Succeeded in adding event, id -> ${data}`);
}).catch((err: BusinessError) => {
logger.error(TAG, `Failed to addEvent. Code: ${err.code}, message: ${err.message}`);
});
// 新增日程
const PARTS: string[] = this.scheduleStartTime.split(' ');
CommonData.SCHEDULE_DATA.push(new ScheduleInfo(this.title, this.location, this.startTime,
this.endTime, this.describe, PARTS[0], this.reminderTimeArray));
TimeUtils.addSchedule(this.startTime, this.endTime);
// 获取Preferences实例
let options: preferences.Options = { name: 'mySchedule' };
this.dataPreferences = preferences.getPreferencesSync(this.context, options);
// 将数据写入缓存的Preferences实例
this.dataPreferences.putSync('schedule', CommonData.SCHEDULE_DATA);
// 通过flush将Preferences实例持久化
this.dataPreferences.flush();
欢迎大家关注公众号<程序猿百晓生>,可以了解到一下知识点。
1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案)
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......
本示例中月视图左右滑动切换日历月份时,只更新一个月数据。周视图每次切换周时,只更新一周的数据,以优化日历左右切换时的性能。年视图使用Canvas绘制显示年视图中每个月,以减少节点数量,同时使用OffscreenCanvasRenderingContext2D离屏绘制,将需要绘制的内容先绘制在缓存区,然后将其转换成图片,一次性绘制到canvas上,以加快绘制速度。
以下是使用DevEco Studio内置的Profiler中的帧率分析工具Frame抓取本案例性能的相关数据(性能耗时数据因设备版本而异,以实测为准):
calendarswitch // har类型
|---pages
| |---CustomCalendarSample.ets // 日历切换场景页面
|---customcalendar
| |---common
| | |---CommonData.ets // 公共数据类
| |---components
| | |---CustomCalendar.ets // 自定义日历组件
| | |---SchedulePoint.ets // 自定义添加日程组件
| |---constant
| | |---Constants.ets // 常量定义
| |---model
| | |---CalendarModel.ets // 日历配置
| | |---OffscreenCanvas.ets // 离屏画布类
| |---utils
| | |---StyleUtils.ets // 样式工具类
| | |---TimeUtils.ets // 时间工具类
| | |---Logger.ets // 日志打印
| |---view
| | |---MonthView.ets // 月视图
| | |---MonthViewItem.ets // 月视图子组件
| | |---WeekView.ets // 周视图
| | |---WeekViewItem.ets // 周视图子组件
| | |---YearView.ets // 年视图
| | |---YearViewItem.ets // 年视图子组件
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。