1# 日历视图切换 2 3### 介绍 4 5本示例介绍使用Swiper实现自定义日历年视图、月视图、周视图左右滑动切换年、月、周的效果。同时使用Tabs实现年视图、月视图、周视图之间的切换效果。使用Canvas来实现日历年视图绘制。还有使用Calendar Kit日历服务实现日程提醒的功能。 6 7### 效果图预览 8 9 10 11**使用说明** 12 131. 进入页面,在月视图上手指往右滑动,可切换到上个月,往左滑动可切换到下个月。 14 152. 在月视图上点击非当日日期,日期上显示绿色边框选中效果。选中当日日期,当日日期显示为红底白字。 16 173. 月视图上点击非当月的日期,可切换到对应月,同时日期显示选中效果。 18 194. 点击“周”按钮,可从月视图切换到周视图,周视图展示的周信息根据月视图之前选中的日期进行跳转。 20 215. 周视图左右滑动可切换下一周和上一周。 22 236. 周视图上选中日期后,点击“月”按钮,可从周视图切换到月视图,月视图展示的月份信息根据周视图之前选中的日期进行月份跳转。 24 257. 周视图切换时,默认根据周视图中第一天的年月信息刷新页面顶部的“xxxx年x月”数据。手动点击周视图日期时,则根据选中的年月信息刷新数据。 26 278. 点击“年”按钮,可从月视图或周视图切换到年视图,年视图展示的年数据根据月视图或周视图之前选中的日期(不选默认今天)所在年份显示对应的年视图数据。 28 299. 在年视图上,往左滑切换到下一年,往右滑切换到上一年。 30 3110. 点击年视图上某个月,会自动切换并显示对应月份的月视图。 32 3311. 点击“月”或“周”按钮从年视图切换到月视图或周视图,会根据之前月视图或周视图选中的日期切换到对应月或周。 34 3512. 打开设备预装的日历应用,打开应用页面会弹窗,点击“同意”。然后在设备“设置”中找到“通知和状态”,选择“日历”,启用“允许通知”。然后打开本示例页面,点击页面右上角的“+”,进入新建日程页面。在新建日程页面,输入“标题”(必填),“地点”(非必填),选择“开始时间”,“结束时间”,“提醒时间”,填写“说明”(非必填)后,点击“添加”,即可添加日程。设备将根据添加的日程进行相应的日程提醒。 36 37### 实现思路 38 39**日历切换功能:** 40 411. 自定义日历组件CustomCalendar。这里参考[日历三方库@xsqd/calendar](https://ohpm.openharmony.cn/#/cn/detail/@xsqd%2Fcalendar)的部分源码使用两个ForEach循环实现日历的月视图和周视图的日期布局效果。通过CalendarViewType条件渲染对应的月视图或周视图。年视图使用Canvas绘制显示年视图中每个月。使用OffscreenCanvasRenderingContext2D在Canvas上进行离屏绘制(主要使用fillText绘制月份,星期,日期等文本数据),它会将需要绘制的内容先绘制在缓存区,然后使用transferToImageBitmap将其转换成图片,一次性绘制到canvas上,以加快绘制速度。源码参考[CustomCalendar.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/components/CustomCalendar.ets)、[YearViewItem.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/YearViewItem.ets)、[MonthViewItem.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/MonthViewItem.ets)、[WeekViewItem.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/WeekViewItem.ets)。 42 43```typescript 44// YearViewItem.ets 45// 年视图 46Grid() { 47 ForEach(this.yearViewList, (monthItem: OffscreenCanvas) => { 48 GridItem() { 49 // TODO: 高性能知识点: 年视图使用Canvas绘制显示年视图中每个月,以减少节点数量,同时使用OffscreenCanvasRenderingContext2D离屏绘制,将需要绘制的内容先绘制在缓存区,然后将其转换成图片,一次性绘制到canvas上,以加快绘制速度。 50 Canvas(monthItem.context) 51 .width($r('app.string.calendar_switch_full_size')) 52 .height($r('app.string.calendar_switch_full_size')) 53 .onReady(() => { 54 // 绘制年视图中一个月的数据 55 // ... 56 // 画月 57 monthItem.offContext.fillText(Constants.MONTHS[monthItem.month-1], Constants.YEAR_VIEW_INIT_THREE, Constants.YEAR_VIEW_MONTH_HEIGHT); 58 // 画星期 59 monthItem.offContext.fillText(Constants.WEEKS[i], horizontalOffset, Constants.YEAR_VIEW_WEEK_HEIGHT); 60 // 画日期 61 monthItem.offContext.fillText(dayIndex.toString(), horizontalOffset, verticalOffset); 62 // ... 63 // 从OffscreenCanvas组件中最近渲染的图像创建一个ImageBitmap对象 64 const IMAGE = monthItem.offContext.transferToImageBitmap(); 65 // 显示给定的ImageBitmap对象 66 monthItem.context.transferFromImageBitmap(IMAGE); 67 }) 68 } 69 }, (monthItem: OffscreenCanvas) => monthItem.year + '' + monthItem.month) 70} 71 72// MonthViewItem.ets 73// 月视图 74Column() { 75 ForEach(this.monthDays, (items: Day[], index: number) => { 76 Row() { 77 ForEach(items, (item: Day) => { 78 this.monthDayBuilder(item, index + 1) 79 }, (item: Day, index: number) => { 80 return item.dayNum + '' + index 81 }) 82 } 83 .width($r('app.string.calendar_switch_full_size')) 84 .justifyContent(FlexAlign.SpaceBetween) 85 }, (item: Day[], index: number) => { 86 return item.reduce((item1, item2) => { 87 return item1 + item2.dayInfo.year + item2.dayInfo.month + item2.dayInfo.date 88 }, '') + index 89 }) 90}.width($r('app.string.calendar_switch_full_size')) 91 92// WeekViewItem.ets 93// 周视图 94Column() { 95 ForEach(this.weekDays, (items: Day[]) => { 96 Row() { 97 ForEach(items, (item: Day) => { 98 this.weekDayBuilder(item) 99 }, (item: Day, index: number) => { 100 return item.dayNum + '' + index; 101 }) 102 } 103 .width($r('app.string.calendar_switch_full_size')) 104 .justifyContent(FlexAlign.SpaceBetween) 105 }, (item: Day[], index: number) => { 106 return item.reduce((item1, item2) => { 107 return item1 + item2.dayInfo.year + item2.dayInfo.month + item2.dayInfo.date 108 }, '') + index 109 }) 110}.width($r('app.string.calendar_switch_full_size')) 111``` 112 1132. 为了实现年视图、月视图、周视图的左右切换年、月、周的效果,通过在Swiper里使用三个自定义日历视图组件实现。以月视图为例,Swiper里放三个MonthViewItem月视图子组件,Swiper中索引0,1,2分别对应上个月,本月,下个月份的数据,通过yearMonth进行指定。年视图和周视图也类似,这里不再赘述。源码参考[MonthView.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/MonthView.ets)。 114 115```typescript 116Swiper() { 117 // 月视图子组件 118 MonthViewItem({ 119 yearMonth: this.lastYearMonth, 120 currentSelectDate: this.currentSelectDate, 121 onDateClick: (year: number, month: number, date: number) => { 122 this.onDateClick(year, month, date); 123 }, 124 CalendarStyle: { 125 textScaling: this.CalendarStyle.textScaling, 126 backgroundColor: this.CalendarStyle.backgroundColor, 127 monthDayColor: this.CalendarStyle.monthDayColor, 128 noMonthDayColor: this.CalendarStyle.noMonthDayColor, 129 lunarColor: this.CalendarStyle.lunarColor 130 } 131 }) 132 MonthViewItem({ 133 yearMonth: this.currentYearMonth, 134 currentSelectDate: this.currentSelectDate, 135 onDateClick: (year: number, month: number, date: number) => { 136 this.onDateClick(year, month, date); 137 }, 138 CalendarStyle: { 139 textScaling: this.CalendarStyle.textScaling, 140 backgroundColor: this.CalendarStyle.backgroundColor, 141 monthDayColor: this.CalendarStyle.monthDayColor, 142 noMonthDayColor: this.CalendarStyle.noMonthDayColor, 143 lunarColor: this.CalendarStyle.lunarColor 144 } 145 }) 146 MonthViewItem({ 147 yearMonth: this.nextYearMonth, 148 currentSelectDate: this.currentSelectDate, 149 onDateClick: (year: number, month: number, date: number) => { 150 this.onDateClick(year, month, date); 151 }, 152 CalendarStyle: { 153 textScaling: this.CalendarStyle.textScaling, 154 backgroundColor: this.CalendarStyle.backgroundColor, 155 monthDayColor: this.CalendarStyle.monthDayColor, 156 noMonthDayColor: this.CalendarStyle.noMonthDayColor, 157 lunarColor: this.CalendarStyle.lunarColor 158 } 159 }) 160} 161``` 162 1633. 年视图、月视图、周视图都是根据Swiper的onAnimationStart事件(切换动画开始时触发该回调)进行年、月、周的切换。以月视图为例,通过oldMonthViewIndex存储上一次的Swiper索引值,然后跟本次切换的索引进行比较,来识别月份是左滑还是右滑。然后根据当前切换后的索引值去刷新所需的月份。例如,假设swiper索引0(7月),swiper索引1(8月),swiper索引2(9月),当前Swiper显示的索引为1。当Swiper右滑从索引1(8月)切换到索引0(7月)时,需要把Swiper里索引2(9月)的月份更新为6月的数据。年视图和周视图也是类似的逻辑,这里不再赘述。源码参考[MonthView.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/MonthView.ets)。 164 165```typescript 166.onAnimationStart((index: number, targetIndex: number, extraInfo: SwiperAnimationEvent) => { 167 if (this.oldMonthViewIndex === targetIndex) { 168 // 如果手指滑动swiper松开时,targetIndex和之前记录子组件索引oldMonthViewIndex一样,说明swiper没有切换子组件,不需要切换月份 169 return; 170 } 171 // 记录子组件索引 172 this.oldMonthViewIndex = targetIndex; 173 // 判断是否右滑切换月份 174 const IS_RIGHT_SLIDE: boolean = (index === 1 && targetIndex === 0) || (index === 0 && targetIndex === 2) || 175 (index === 2 && targetIndex === 1); 176 // TODO: 高性能知识点: 左右滑动切换月时,每次切换月只更新一个月的数据,以优化月视图左右切换时的性能。年视图和周视图也类似,这里不再赘述。 177 // 右滑切换到上个月 178 if (IS_RIGHT_SLIDE) { 179 // 将当前月份设置为上个月 180 this.currentShowYear = TimeUtils.getLastYear(this.currentShowYear, this.currentShowMonth); 181 this.currentShowMonth = TimeUtils.getLastMonth(this.currentShowYear, this.currentShowMonth); 182 this.onChangeYearMonth(this.currentShowYear, this.currentShowMonth); 183 if (targetIndex === 0) { 184 // swiper索引右滑到0时,修改swiper索引2的月份为当前月份(索引0)的上一个月。比如,假设swiper索引0(7月),swiper索引1(8月),swiper索引2(9月)。当右滑切换到索引0(7月)时,需要把索引2(9月)的月份改成6月。 185 // 修改swiper索引2的月份为当前月份(索引0)的上一个月 186 this.nextYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 187 } else if (targetIndex === 1) { 188 // swiper索引右滑到1时,修改swiper索引0的月份为当前月份(索引1)的上一个月。 189 this.lastYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 190 } else if (targetIndex === 2) { 191 // swiper索引右滑到2时,修改swiper索引1的月份为当前月份(索引2)的上一个月。 192 this.currentYearMonth = TimeUtils.getLastYearMonth(this.currentShowYear, this.currentShowMonth); 193 } 194 } else { 195 // 右滑切换到下个月 196 // 将当前月份设置为下个月 197 this.currentShowYear = TimeUtils.getNextYear(this.currentShowYear, this.currentShowMonth); 198 this.currentShowMonth = TimeUtils.getNextMonth(this.currentShowYear, this.currentShowMonth); 199 this.onChangeYearMonth(this.currentShowYear, this.currentShowMonth); 200 if (targetIndex === 0) { 201 // swiper索引左滑到0时,修改swiper索引1的月份为当前月份(索引0)的下一个月。 202 this.currentYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 203 } else if (targetIndex === 1) { 204 // swiper索引左滑到1时,修改swiper索引2的月份为当前月份(索引1)的下一个月。 205 this.nextYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 206 } else if (targetIndex === 2) { 207 // swiper索引左滑到2时,修改swiper索引0的月份为当前月份(索引2)的下一个月。 208 this.lastYearMonth = TimeUtils.getNextYearMonth(this.currentShowYear, this.currentShowMonth); 209 } 210 } 211}) 212``` 213 2144. 年视图、月视图、周视图之间的切换通过changeIndex控制Tabs切换到指定页签,使用视图控制器的swiperRefresh方法刷新对应视图数据。从周视图切换到月视图时,月视图需要刷新的月份数据根据目前选中的日期currentSelectDay中的年月信息设置到MonthViewItem的yearMonth,然后通过触发yearMonth的updateMonthData监听进行月份数据刷新(getMonthViewData)。从月视图切换到周视图时,周视图需要刷新的周数据,也是根据目前选中的日期currentSelectDay中的年月日信息。通过计算选中日期到今天相差几周,来计算需要传入WeekViewItem的weekNum,触发updateWeekData监听,进行周数据刷新(getWeekViewData)。年视图是通过触发YearViewItem中year的updateYearData监听,更新年数据。在年视图中点击某个月,会根据点击的月份调用月视图控制器calendarMonthController的swiperYearToMonthRefresh方法切换到对应月份的月视图。从月视图或周视图切换到年视图,是根据选中日期所在的年份进行对应年份年视图切换。源码参考[CustomCalendarSample.ets](./casesfeature/calendarswitch/src/main/ets/pages/CustomCalendarSample.ets)、[YearViewItem.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/YearViewItem.ets)、[MonthViewItem.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/MonthViewItem.ets)、[WeekViewItem.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/view/WeekViewItem.ets)。 215 216```typescript 217// CustomCalendarSample.ets 218this.tabController.changeIndex(index); 219if (this.tabSelectedIndex === CalendarViewType.MONTH) { 220 this.currentShowYear = this.currentSelectDay.year; 221 this.currentShowMonth = this.currentSelectDay.month; 222 // 刷新月视图 223 this.calendarMonthController.swiperRefresh(CalendarViewType.MONTH); 224} else if (this.tabSelectedIndex === CalendarViewType.WEEK) { 225 this.currentShowYear = this.currentSelectDay.year; 226 this.currentShowMonth = this.currentSelectDay.month; 227 // 刷新周视图 228 this.calendarWeekController.swiperRefresh(CalendarViewType.WEEK); 229} else if (this.tabSelectedIndex === CalendarViewType.YEAR) { 230 // 刷新年视图 231 this.calendarYearController.swiperRefresh(CalendarViewType.YEAR); 232} 233 234// ... 235 236onMonthClick: (year: number, month: number) => { 237 if (this.tabController) { 238 // 切到月视图 239 this.tabController.changeIndex(1); 240 // 刷新年月信息标题 241 this.currentShowYear = year; 242 this.currentShowMonth = month; 243 // 刷新在年视图上点击月后要跳转的月视图数据 244 this.calendarMonthController.swiperYearToMonthRefresh(year, month); 245 } 246} 247 248// MonthViewItem.ets 249/** 250 * 获取指定月份数据 251 */ 252getMonthViewData(year: number, month: number) { 253 this.monthDays = [...TimeUtils.byMonthDayForYear(year, month)]; 254} 255 256// WeekViewItem.ets 257/** 258 * 获取指定周数据 259 */ 260getWeekViewData(weekNum: number) { 261 this.weekDays = [...TimeUtils.getWeekDays(weekNum)]; 262} 263 264// YearViewItem.ets 265/** 266 * 更新年数据 267 */ 268updateYearData() { 269 this.yearViewList = []; 270 for (let i = 1; i <= Constants.MONTHS_NUM; i++) { 271 this.yearViewList.push(new OffscreenCanvas(this.year, i)); 272 } 273} 274``` 275 276**日程提醒功能(不支持rk3568):** 277 278前置条件:需要在module.json5中配置日历读写权限"ohos.permission.READ_CALENDAR"、"ohos.permission.WRITE_CALENDAR"。 279 2801. 调用requestPermissionsFromUser向用户申请系统日历读写权限。通过getCalendarManager获取管理日历对象,使用getCalendar获取日历对象,然后使用createCalendar创建自己的日历账户,通过配置CalendarConfig中enableReminder为true启用日程提醒功能。源码参考[SchedulePoint.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/components/SchedulePoint.ets)。 281 282```typescript 283 const permissions: Permissions[] = ['ohos.permission.READ_CALENDAR', 'ohos.permission.WRITE_CALENDAR']; 284// 获取AtManager实例 285let atManager = abilityAccessCtrl.createAtManager(); 286// 向用户申请系统日历读写权限 287atManager.requestPermissionsFromUser(this.context, permissions).then((result: PermissionRequestResult) => { 288 logger.info(TAG, `get Permission success, result: ${JSON.stringify(result)}`); 289 // 根据上下文获取CalendarManager对象,用于管理日历。 290 this.calendarMgr = calendarManager.getCalendarManager(this.context); 291 // 获取Calendar对象 292 this.calendarMgr.getCalendar(this.myCalendarAccount).then((data: calendarManager.Calendar) => { 293 this.calendar = data; 294 // 设置日历配置信息 295 this.calendar.setConfig(this.config).then(() => { 296 logger.info(TAG, `Succeeded in setting config, data->${JSON.stringify(this.config)}`); 297 }).catch((err: BusinessError) => { 298 logger.error(TAG, `Failed to set config. Code: ${err.code}, message: ${err.message}`); 299 }); 300 }).catch(() => { 301 // 如果日历账户不存在,则创建日历账户 302 this.calendarMgr?.createCalendar(this.myCalendarAccount).then((data: calendarManager.Calendar) => { 303 // 请确保日历账户创建成功后,再进行后续相关操作 304 this.calendar = data; 305 // 设置日历账户 306 this.calendar?.setConfig(this.config).then(() => { 307 logger.info(TAG, `Succeeded in setting config, data->${JSON.stringify(this.config)}`); 308 }).catch((err: BusinessError) => { 309 logger.error(TAG, `Failed to set config. Code: ${err.code}, message: ${err.message}`); 310 }); 311 }).catch((error: BusinessError) => { 312 logger.error(TAG, `Failed to create calendar. Code: ${error.code}, message: ${error.message}`); 313 }); 314 }); 315}).catch((error: BusinessError) => { 316 logger.error(TAG, `get Permission error, error. Code: ${error.code}, message: ${error.message}`); 317}) 318``` 319 3202. 配置日程参数calendarManager.Event,然后传入addEvent创建日程,Calendar Kit日历服务会根据创建的日程进行相应的日程提醒。同时使用持久化preferences存储添加的日程信息,用于月视图和周视图中显示相应的日程点。源码参考[SchedulePoint.ets](./casesfeature/calendarswitch/src/main/ets/customcalendar/components/SchedulePoint.ets)。 321 322```typescript 323// 配置日程参数 324const EVENT_NOT_REPEATED: calendarManager.Event = { 325 // 日程标题 326 title: this.title, 327 // 地点 328 location: { location: this.location }, 329 // 日程类型,NORMAL:普通日程,例如会议,闹钟等日常提醒的日程。 IMPORTANT:重要日程,例如结婚纪念日等具有重要意义的日期,不推荐三方开发者使用,重要日程类型不支持一键服务跳转功能及无法自定义提醒时间。 330 type: calendarManager.EventType.NORMAL, 331 // 日程开始时间,需要13位时间戳。 332 startTime: this.startTime.getTime(), 333 // 日程结束时间,需要13位时间戳。 334 endTime: this.endTime.getTime(), 335 // 日程提醒时间,单位为分钟。填写x分钟,即距开始时间提前x分钟提醒,不填时,默认为不提醒。为负值时表示延期多长时间提醒。 336 reminderTime: this.reminderTimeArray 337}; 338// 创建日程 339this.calendar?.addEvent(EVENT_NOT_REPEATED).then((data: number) => { 340 logger.info(TAG, `Succeeded in adding event, id -> ${data}`); 341}).catch((err: BusinessError) => { 342 logger.error(TAG, `Failed to addEvent. Code: ${err.code}, message: ${err.message}`); 343}); 344// 新增日程 345const PARTS: string[] = this.scheduleStartTime.split(' '); 346CommonData.SCHEDULE_DATA.push(new ScheduleInfo(this.title, this.location, this.startTime, 347 this.endTime, this.describe, PARTS[0], this.reminderTimeArray)); 348TimeUtils.addSchedule(this.startTime, this.endTime); 349// 获取Preferences实例 350let options: preferences.Options = { name: 'mySchedule' }; 351this.dataPreferences = preferences.getPreferencesSync(this.context, options); 352// 将数据写入缓存的Preferences实例 353this.dataPreferences.putSync('schedule', CommonData.SCHEDULE_DATA); 354// 通过flush将Preferences实例持久化 355this.dataPreferences.flush(); 356``` 357 358### 高性能知识点 359 360本示例中月视图左右滑动切换日历月份时,只更新一个月数据。周视图每次切换周时,只更新一周的数据,以优化日历左右切换时的性能。年视图使用Canvas绘制显示年视图中每个月,以减少节点数量,同时使用OffscreenCanvasRenderingContext2D离屏绘制,将需要绘制的内容先绘制在缓存区,然后将其转换成图片,一次性绘制到canvas上,以加快绘制速度。 361 362### 参考资料 363 364[日历三方库@xsqd/calendar](https://ohpm.openharmony.cn/#/cn/detail/@xsqd%2Fcalendar) 365 366[国际化-I18n](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-localization-kit/js-apis-i18n.md) 367 368[离屏绘制](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkui/arkui-js/js-offscreencanvasrenderingcontext2d.md) 369 370[Canvas](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-components-canvas-canvas.md) 371 372[Calendar Kit](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/calendarmanager/calendarmanager-overview.md) 373 374[preferences](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/reference/apis-arkdata/js-apis-data-preferences.md) 375 376### 工程目录 377 378``` 379calendarswitch 380|---pages 381| |---CustomCalendarSample.ets // 日历切换场景页面 382|---customcalendar 383| |---common 384| | |---CommonData.ets // 公共数据类 385| |---components 386| | |---CustomCalendar.ets // 自定义日历组件 387| | |---SchedulePoint.ets // 自定义添加日程组件 388| |---constant 389| | |---Constants.ets // 常量定义 390| |---model 391| | |---CalendarModel.ets // 日历配置 392| | |---OffscreenCanvas.ets // 离屏画布类 393| |---utils 394| | |---StyleUtils.ets // 样式工具类 395| | |---TimeUtils.ets // 时间工具类 396| | |---Logger.ets // 日志打印 397| |---view 398| | |---MonthView.ets // 月视图 399| | |---MonthViewItem.ets // 月视图子组件 400| | |---WeekView.ets // 周视图 401| | |---WeekViewItem.ets // 周视图子组件 402| | |---YearView.ets // 年视图 403| | |---YearViewItem.ets // 年视图子组件 404``` 405 406### 相关权限 407 408日程提醒功能需要在module.json5中配置日历读写权限[ohos.permission.READ_CALENDAR](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/AccessToken/permissions-for-all.md#ohospermissionread_calendar)、[ohos.permission.WRITE_CALENDAR](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/AccessToken/permissions-for-all.md#ohospermissionwrite_calendar)。 409 410### 依赖 411 412不涉及。 413 414### 约束与限制 415 4161.本示例仅支持标准系统上运行。 417 4182.本示例已适配API version 12版本SDK。 419 4203.本示例需要使用DevEco Studio 5.0.0 Release及以上版本才可编译运行。 421 422### 下载 423 424如需单独下载本工程,执行如下命令: 425 426``` 427git init 428git config core.sparsecheckout true 429echo code/UI/CalendarViewSwitch > .git/info/sparse-checkout 430git remote add origin https://gitee.com/openharmony/applications_app_samples.git 431git pull origin master 432```