• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16import {
17  CustomCalendar, // 自定义日历组件
18  CalendarViewType, // 自定义日历类型:年视图YEAR,月视图MONTH,周视图WEEK。
19  CalendarController, // 自定义日历控制器。用于控制年、月、周视图间切换场景下刷新日期数据。
20  DayInfo // 日期信息类
21} from '../customcalendar/components/CustomCalendar'; // 自定义日历组件
22import { SchedulePoint } from '../customcalendar/components/SchedulePoint'; // 自定义添加日程组件
23import { display } from '@kit.ArkUI'; // 屏幕属性模块
24import CommonData from '../customcalendar/common/CommonData';
25import { DeviceType } from '../customcalendar/model/CalendarModel';
26
27// 布局权重
28const LAYOUT_WEIGHT_ONE: number = 1;
29const LAYOUT_WEIGHT_THREE: number = 3;
30// 字体缩放倍数
31const TEXT_SCALING: number = 0.95;
32// 当前年
33const TODAY_YEAR: number = new Date().getFullYear();
34// 当前月
35const TODAY_MONTH: number = new Date().getMonth() + 1;
36// 内边距。和下面的自定义的分段按钮customSegmentButtonItem有关联
37const PADDING: number = 15;
38// 自定义分段按钮中Column宽度
39const COLUMN_WIDTH: number = 50;
40// 自定义分段按钮白色滑块空隙间距
41const GAP_SPACE: number = 6;
42// 自定义分段按钮'月'Text的宽度
43const CUSTOM_SEGMENT_BUTTON_MONTH_WIDTH: number = 30;
44// 自定义分段按钮选中和未选中的字体粗细
45const FONT_WEIGHT_FOUR_HUNDRED: number = 400;
46const FONT_WEIGHT_FIVE_HUNDRED: number = 500;
47// 月份数组
48const MONTHS: string[] =
49  ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'];
50// 年月信息标题字体大小
51const FONT_SIZE: number = 18;
52
53/**
54 * 功能描述:本示例介绍如何使用自定义日历组件CustomCalendar实现日历年视图,月视图,周视图以及视图切换功能。还有如何使用Calendar Kit日历服务实现日程提醒的功能。
55 *
56 * 推荐场景:需要使用左右滑动切换年视图,月视图,周视图以及需要添加日程提醒的应用场景
57 *
58 * 核心组件:
59 * 1.CustomCalendar
60 * 2.SchedulePoint
61 *
62 * 实现步骤:
63 *  日历切换场景:
64 *  1.使用Tabs进行年、月、周视图页面布局。
65 *  2.调用自定义日历组件CustomCalendar组件分别在TabContent中显示对应年、月、周视图。
66 *  3.点击自定义分段按钮customSegmentButton进行年、月、周视图间切换。使用视图控制器的swiperRefresh方法刷新对应视图数据。
67 *  日程提醒场景:
68 *  1.通过getCalendarManager获取管理日历对象,使用getCalendar获取日历对象,然后使用createCalendar创建自己的日历账户,通过配置
69 *  CalendarConfig中enableReminder为true启用日程提醒功能。
70 *  2.配置日程参数calendarManager.Event,然后传入addEvent创建日程,Calendar Kit日历服务会根据创建的日程进行相应的日程提醒。同时使用持久
71 *  化preferences存储添加的日程信息,用于月视图和周视图中显示相应的日程点。
72 */
73@Component
74export struct CustomCalendarSamplePage {
75  // 屏幕宽度
76  @State screenWidth: number = 0;
77  // 当前显示的年份
78  @State currentShowYear: number = TODAY_YEAR;
79  // 当前显示的月份
80  @State currentShowMonth: number = TODAY_MONTH;
81  // 当前月视图或周视图中选中的日期
82  @State currentSelectDay: DayInfo =
83    new DayInfo(new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate(), 0);
84  // 是否隐藏年、月、周视图中年月信息标题
85  @State isYearMonthHidden: boolean = true;
86  // 当前选中的自定义分段按钮。0年视图,1月视图,2周视图
87  @State currentIndex: number = 1;
88  // 自定义分段按钮左侧边距
89  @State indicatorLeftMargin: number = 0;
90  // 自定义分段按钮白色滑块宽度
91  @State indicatorWidth: number = 0;
92  // 记录自定义分段按钮切换的index。如果切换到年视图,隐藏年月信息标题中月份
93  @State tabSelectedIndex: number = 1;
94  // 自定义分段按钮滑动动画时长
95  private animationDuration: number = 300;
96  // Tabs控制器
97  private tabController: TabsController = new TabsController();
98  // 自定义日历年视图控制器
99  private calendarYearController = new CalendarController();
100  // 自定义日历月视图控制器
101  private calendarMonthController = new CalendarController();
102  // 自定义日历周视图控制器
103  private calendarWeekController = new CalendarController();
104  // 检查设备是否可折叠
105  private isFoldable: boolean = false;
106  // 折叠设备屏幕显示模式回调
107  private callback: Callback<display.FoldDisplayMode> = (mode: display.FoldDisplayMode) => {
108    // 可折叠设备的显示模式改变时(如展开或者折叠),重新获取屏幕宽度
109    this.screenWidth = this.getCurrentScreenWidth();
110    // 重新计算indicatorLeftMargin
111    if (this.currentIndex === CalendarViewType.YEAR) {
112      this.indicatorLeftMargin = (this.screenWidth - PADDING * 2) / 6 * 1 - COLUMN_WIDTH / 2;
113    } else if (this.currentIndex === CalendarViewType.MONTH) {
114      this.indicatorLeftMargin = (this.screenWidth - PADDING * 2) / 2 - COLUMN_WIDTH / 2;
115    } else if (this.currentIndex === CalendarViewType.WEEK) {
116      this.indicatorLeftMargin = (this.screenWidth - PADDING * 2) / 6 * 5 - COLUMN_WIDTH / 2;
117    }
118    if (mode === display.FoldDisplayMode.FOLD_DISPLAY_MODE_FULL) {
119      // 折叠屏展开态显示
120      CommonData.DEVICE_TYPE = DeviceType.EXPAND_FOLD;
121    } else if (mode === display.FoldDisplayMode.FOLD_DISPLAY_MODE_MAIN) {
122      // 折叠屏折叠态显示
123      CommonData.DEVICE_TYPE = DeviceType.PHONE;
124    }
125  };
126  // 依据cases工程Navigation的mode属性说明,如使用Auto,窗口宽度>=600vp时,采用Split模式显示;窗口宽度<600vp时,采用Stack模式显示。
127  private readonly DEVICESIZE: number = 600;
128  // 不传isPlugin,默认为true。作为插件使用。设置false,适配cases。
129  isPlugin: boolean = true;
130
131  /**
132   * 获取当前屏幕宽度
133   */
134  getCurrentScreenWidth(): number {
135    let screenWidth: number = px2vp(display.getDefaultDisplaySync().width);
136    // 适配cases中Navigation在不同mode时,计算相对需要使用的屏幕宽度。当屏幕宽度大于600vp时,cases工程Navigation的mode采用Split模式显
137    // 示,需要重新计算实际页面所需的屏幕宽度。
138    if (!this.isPlugin && screenWidth >= this.DEVICESIZE) {
139      return screenWidth / 2;
140    } else {
141      return screenWidth;
142    }
143  }
144
145  aboutToAppear() {
146    // 检查设备是否可折叠。false表示不可折叠,true表示可折叠。
147    this.isFoldable = display.isFoldable();
148    CommonData.IS_FOLD = this.isFoldable;
149    if (this.isFoldable) {
150      // 如果是可折叠设备,注册折叠设备屏幕显示模式变化监听
151      display.on('foldDisplayModeChange', this.callback);
152      if (display.getFoldDisplayMode() === display.FoldDisplayMode.FOLD_DISPLAY_MODE_FULL) {
153        // 设置折叠屏展开态
154        CommonData.DEVICE_TYPE = DeviceType.EXPAND_FOLD;
155      }
156    }
157    // 获取屏幕宽度
158    this.screenWidth = this.getCurrentScreenWidth();
159    // 初始化自定义分段按钮白色滑块的位置,本案例默认首次加载显示月视图。由于onAreaChange获取indicatorLeftMargin有延迟,首次加载会出现白色
160    // 滑块跳变,所以这里计算indicatorLeftMargin的初始位置。
161    this.indicatorLeftMargin = (this.screenWidth - COLUMN_WIDTH) / 2 - PADDING;
162    // 判断是否是rk3568。用屏幕宽度480vp来判断
163    if (px2vp(display.getDefaultDisplaySync().width) === 480) {
164      CommonData.DEVICE_TYPE = DeviceType.RK;
165    }
166  }
167
168  aboutToDisappear() {
169    if (this.isFoldable) {
170      // 关闭显示设备变化的监听
171      display.off('foldDisplayModeChange', this.callback);
172    }
173  }
174
175  /**
176   * 年月信息标题。月视图和周视图显示年月信息,年视图只显示年信息。周视图中如果选中了日期,则优先根据选中日期显示年月信息。
177   */
178  @Builder
179  yearMonthTitle() {
180    Row() {
181      Text(`${this.currentShowYear}年 ${this.tabSelectedIndex === CalendarViewType.YEAR ? '' :
182      MONTHS[this.currentShowMonth-1]}`)
183        .fontSize(FONT_SIZE * TEXT_SCALING)
184        .fontWeight(FONT_WEIGHT_FIVE_HUNDRED)
185      // 自定义添加日程组件
186      // monthViewController:     可选项。传入该控制器,添加日程后,对应日程点会刷新到月视图上
187      // weekViewController:      可选项。传入该控制器,添加日程后,对应日程点会刷新到周视图上
188      SchedulePoint({
189        monthViewController: this.calendarMonthController,
190        weekViewController: this.calendarWeekController
191      })
192    }
193    .padding({ left: $r('app.integer.calendar_switch_size_ten'), right: $r('app.integer.calendar_switch_size_ten') })
194    .justifyContent(FlexAlign.SpaceBetween)
195    .width($r('app.string.calendar_switch_full_size'))
196    .height($r('app.integer.calendar_switch_size_thirty'))
197  }
198
199  /**
200   * 自定义分段按钮
201   */
202  @Builder
203  customSegmentButton() {
204    Stack({ alignContent: Alignment.TopStart }) {
205      Row() {
206      }
207      .width($r('app.string.calendar_switch_full_size'))
208      .height($r('app.integer.calendar_switch_size_thirty_five'))
209      .backgroundColor($r('app.color.calendar_switch_segment_button_row_bgcolor'))
210      .borderRadius($r('app.integer.calendar_switch_border_radius'))
211      .layoutWeight(LAYOUT_WEIGHT_THREE)
212
213      Column() {
214        Row() {
215        }
216        .borderRadius($r('app.integer.calendar_switch_border_radius'))
217        .height($r('app.string.calendar_switch_full_size'))
218        .width((this.screenWidth - PADDING * 2) / 3 - GAP_SPACE)
219        .backgroundColor(Color.White)
220      }
221      .height($r('app.integer.calendar_switch_size_thirty_five'))
222      .width(COLUMN_WIDTH)
223      .margin({ left: this.indicatorLeftMargin })
224      .padding({
225        top: $r('app.integer.calendar_switch_size_three'),
226        bottom: $r('app.integer.calendar_switch_size_three')
227      })
228
229      Row() {
230        this.customSegmentButtonItem(CalendarViewType.YEAR, '年')
231        this.customSegmentButtonItem(CalendarViewType.MONTH, '月')
232        this.customSegmentButtonItem(CalendarViewType.WEEK, '周')
233      }
234      .width($r('app.string.calendar_switch_full_size'))
235      .height($r('app.integer.calendar_switch_size_thirty_five'))
236      .borderRadius($r('app.integer.calendar_switch_border_radius'))
237      .backgroundColor(Color.Transparent)
238      .layoutWeight(LAYOUT_WEIGHT_THREE)
239    }
240    .width($r('app.string.calendar_switch_full_size'))
241    .height($r('app.integer.calendar_switch_size_thirty_five'))
242    .margin({
243      top: $r('app.integer.calendar_switch_size_ten'),
244      bottom: $r('app.integer.calendar_switch_margin_size_twelve')
245    })
246  }
247
248  /**
249   * 自定义分段按钮项
250   * @param index 自定义分段按钮索引。这里对应自定义日历视图类型。0:年视图YEAR,1:月视图MONTH,2:周视图WEEK
251   * @param name 自定义分段按钮名。这里对应'年','月','周'
252   */
253  @Builder
254  customSegmentButtonItem(index: number, name: string) {
255    Column() {
256      Text(name)
257        .width(CUSTOM_SEGMENT_BUTTON_MONTH_WIDTH)
258        .textAlign(TextAlign.Center)
259        .height($r('app.integer.calendar_switch_size_thirty_five'))
260        .fontSize($r('app.integer.calendar_switch_size_fourteen'))
261        .fontColor(this.currentIndex === index ? Color.Black :
262        $r('app.color.calendar_switch_segment_button_font_color'))
263        .fontWeight(this.currentIndex === index ? FONT_WEIGHT_FIVE_HUNDRED : FONT_WEIGHT_FOUR_HUNDRED)
264    }
265    .width($r('app.string.calendar_switch_full_size'))
266    .height($r('app.integer.calendar_switch_size_thirty_five'))
267    .layoutWeight(LAYOUT_WEIGHT_ONE)
268    .onClick(() => {
269      if (index === this.tabSelectedIndex) {
270        // 点击同一个自定义分段按钮项,不做切换,避免冗余操作
271        return;
272      }
273      this.tabSelectedIndex = index;
274      this.tabController.changeIndex(index);
275      this.currentShowYear = this.currentSelectDay.year;
276      this.currentShowMonth = this.currentSelectDay.month;
277      // TODO 知识点:点击自定义分段按钮customSegmentButton进行年、月、周视图间切换。使用视图控制器的swiperRefresh方法刷新对应视图数据。
278      if (this.tabSelectedIndex === CalendarViewType.MONTH) {
279        // 刷新月视图
280        this.calendarMonthController.swiperRefresh(CalendarViewType.MONTH);
281      } else if (this.tabSelectedIndex === CalendarViewType.WEEK) {
282        // 刷新周视图
283        this.calendarWeekController.swiperRefresh(CalendarViewType.WEEK);
284      } else if (this.tabSelectedIndex === CalendarViewType.YEAR) {
285        // 刷新年视图
286        this.calendarYearController.swiperRefresh(CalendarViewType.YEAR);
287      }
288    })
289  }
290
291  /**
292   * 自定义分段按钮切换动画
293   * @param duration 动画时长
294   * @param leftMargin 自定义分段按钮左侧边距
295   */
296  startAnimateTo(duration: number, leftMargin: number) {
297    animateTo({
298      duration: duration, // 动画时长
299      curve: Curve.Linear, // 动画曲线
300      iterations: 1, // 播放次数
301      playMode: PlayMode.Normal, // 动画模式
302    }, () => {
303      this.indicatorLeftMargin = leftMargin;
304    })
305  }
306
307  build() {
308    Column() {
309      // 年月信息标题(包含添加日程组件)
310      this.yearMonthTitle()
311      // 自定义分段按钮
312      this.customSegmentButton()
313
314      Tabs({ barPosition: BarPosition.End, index: CalendarViewType.MONTH, controller: this.tabController }) {
315        TabContent() {
316          Column() {
317            // 自定义年视图
318            // calendarViewType:   必选项。自定义日历类型。YEAR年视图 MONTH月视图 WEEK周视图
319            // onMonthClick:       可选项。年视图月份点击回调。返回年视图点击的年月信息。仅用于年视图。
320            // onChangeYearMonth:  可选项。年、月、周视图左右滑动切换回调。返回左右切换年、月、周后的年月信息,其中年视图切换实际只返回切换后年份信息。
321            // calendarSwitch:     可选项。年、月、周视图切换场景的相关设置。
322            // - controller:       可选项。自定义日历控制器,用于视图切换后的数据刷新。
323            // - currentSelectDay: 可选项。记录月、周视图中点击选中的日期信息。
324            // - isYearMonthHidden:可选项。是否隐藏自定义日历年、月、周视图中自带的年月信息标题。
325            CustomCalendar({
326              calendarViewType: CalendarViewType.YEAR,
327              onMonthClick: (year: number, month: number) => {
328                if (this.tabController) {
329                  // 切到月视图
330                  this.tabController.changeIndex(1);
331                  // 刷新年月信息标题
332                  this.currentShowYear = year;
333                  this.currentShowMonth = month;
334                  // 刷新在年视图上点击月后要跳转的月视图数据
335                  this.calendarMonthController.swiperYearToMonthRefresh(year, month);
336                }
337              },
338              onChangeYearMonth: (year: number, month: number) => {
339                this.currentShowYear = year;
340              },
341              calendarSwitch: {
342                controller: this.calendarYearController,
343                currentSelectDay: this.currentSelectDay,
344                isYearMonthHidden: this.isYearMonthHidden
345              }
346            })
347          }
348          .width($r('app.string.calendar_switch_full_size'))
349          .height($r('app.string.calendar_switch_full_size'))
350        }
351
352        TabContent() {
353          Column() {
354            // 自定义月视图
355            // calendarViewType:   必选项。自定义日历类型。YEAR年视图 MONTH月视图 WEEK周视图
356            // calendarStyle:      可选项。自定义日历样式。仅用于月、周视图。
357            // - textScaling:      可选项。月视图和周视图中的公历、农历、星期、年月信息标题文字缩放比例。
358            // - backgroundColor:  可选项。今天选中日期的背景色。
359            // - monthDayColor:    可选项。本月公历日期颜色。
360            // - noMonthDayColor:  可选项。非本月公历日期颜色,仅对月视图有效。
361            // - lunarColor:       可选项。本月农历字体颜色。
362            // onDateClick:        可选项。日期点击回调。返回点击日期的年月日信息。仅用于月、周视图。
363            // onChangeYearMonth:  可选项。年、月、周视图左右滑动切换回调,返回左右切换视图后的年月信息,其中年视图切换实际只返回切换后年份信息。
364            // calendarSwitch:     可选项。用于年、月、周视图切换场景的相关设置。
365            // - controller:       可选项。自定义日历控制器,用于视图切换后的数据刷新。
366            // - currentSelectDay: 可选项。记录月、周视图中点击选中的日期信息。
367            // - isYearMonthHidden:可选项。是否隐藏自定义日历年、月、周视图中自带的年月信息标题。
368            CustomCalendar({
369              calendarViewType: CalendarViewType.MONTH,
370              calendarStyle: {
371                textScaling: TEXT_SCALING,
372                backgroundColor: Color.Red,
373                monthDayColor: Color.Black,
374                noMonthDayColor: Color.Gray,
375                lunarColor: Color.Gray
376              },
377              onDateClick: (year: number, month: number, date: number) => {
378                this.currentSelectDay.year = year;
379                this.currentSelectDay.month = month;
380                this.currentSelectDay.date = date;
381              },
382              onChangeYearMonth: (year: number, month: number) => {
383                this.currentShowYear = year;
384                this.currentShowMonth = month;
385              },
386              calendarSwitch: {
387                controller: this.calendarMonthController,
388                currentSelectDay: this.currentSelectDay,
389                isYearMonthHidden: this.isYearMonthHidden,
390              }
391            })
392          }
393          .width($r('app.string.calendar_switch_full_size'))
394          .height($r('app.string.calendar_switch_full_size'))
395        }
396
397        TabContent() {
398          Column() {
399            // 自定义周视图
400            // calendarViewType:   必选项。自定义日历类型。YEAR年视图 MONTH月视图 WEEK周视图
401            // calendarStyle:      可选项。自定义日历样式。仅用于月、周视图。
402            // - textScaling:      可选项。月视图和周视图中的公历、农历、星期、年月信息标题文字缩放比例。
403            // - backgroundColor:  可选项。今天选中日期的背景色。
404            // - monthDayColor:    可选项。本月公历日期颜色。
405            // - noMonthDayColor:  可选项。非本月公历日期颜色,仅对月视图有效。
406            // - lunarColor:       可选项。本月农历字体颜色。
407            // onDateClick:        可选项。日期点击回调。返回点击日期的年月日信息。仅用于月、周视图。
408            // onChangeYearMonth:  可选项。年、月、周视图左右滑动切换回调,返回左右切换视图后的年月信息,其中年视图切换实际只返回切换后年份信息。
409            // calendarSwitch:     可选项。用于年、月、周视图切换场景的相关设置。
410            // - controller:       可选项。自定义日历控制器,用于视图切换后的数据刷新。
411            // - currentSelectDay: 可选项。记录月、周视图中点击选中的日期信息。
412            // - isYearMonthHidden:可选项。是否隐藏自定义日历年、月、周视图中自带的年月信息标题。
413            CustomCalendar({
414              calendarViewType: CalendarViewType.WEEK,
415              calendarStyle: {
416                textScaling: TEXT_SCALING,
417                backgroundColor: Color.Red,
418                monthDayColor: Color.Black,
419                lunarColor: Color.Gray
420              },
421              onDateClick: (year: number, month: number, date: number) => {
422                this.currentSelectDay.year = year;
423                this.currentSelectDay.month = month;
424                this.currentSelectDay.date = date;
425              },
426              onChangeYearMonth: (year: number, month: number) => {
427                this.currentShowYear = year;
428                this.currentShowMonth = month;
429              },
430              calendarSwitch: {
431                controller: this.calendarWeekController,
432                currentSelectDay: this.currentSelectDay,
433                isYearMonthHidden: this.isYearMonthHidden
434              }
435            })
436          }
437          .width($r('app.string.calendar_switch_full_size'))
438          .height($r('app.string.calendar_switch_full_size'))
439        }
440      }
441      .animationDuration(this.animationDuration)
442      .onAnimationStart((index: number, targetIndex: number, event: TabsAnimationEvent) => {
443        // 在年视图中点击月,切换到月视图时,需要显示'年月信息标题'中的的月份信息
444        if (index === CalendarViewType.YEAR && targetIndex === CalendarViewType.MONTH) {
445          this.tabSelectedIndex = CalendarViewType.MONTH;
446        }
447        // 切换动画开始时触发该回调。白色滑块跟着页面一起滑动。
448        this.currentIndex = targetIndex;
449        if (targetIndex === CalendarViewType.YEAR) {
450          // 传入自定义分段按钮左侧边距 点击“年”的分段按钮时,分段按钮白色滑块移动到屏幕1/6位置
451          this.startAnimateTo(this.animationDuration, (this.screenWidth - PADDING * 2) / 6 * 1 - COLUMN_WIDTH / 2);
452        } else if (targetIndex === CalendarViewType.MONTH) {
453          // 传入自定义分段按钮左侧边距 点击“月”的分段按钮时,分段按钮白色滑块移动到屏幕3/6位置
454          this.startAnimateTo(this.animationDuration, (this.screenWidth - PADDING * 2) / 2 - COLUMN_WIDTH / 2);
455        } else if (targetIndex === CalendarViewType.WEEK) {
456          // 传入自定义分段按钮左侧边距 点击“周”的分段按钮时,分段按钮白色滑块移动到屏幕5/6位置
457          this.startAnimateTo(this.animationDuration, (this.screenWidth - PADDING * 2) / 6 * 5 - COLUMN_WIDTH / 2);
458        }
459      })
460      .onAppear(() => {
461        // TODO: 高性能知识点: 组件挂载显示后触发此回调,预加载年视图数据,避免首次切换到年视图时出现卡顿问题
462        // 针对月视图切换周视图场景,需要预加载周视图(索引2),不然在月视图切换月份,选择日期后,再切换到周视图,周视图不会刷新
463        this.tabController.preloadItems([0, 2]); // 索引0对应年视图,索引2对应周视图
464      })
465      .layoutWeight(LAYOUT_WEIGHT_ONE)
466      .scrollable(false)
467      .barHeight($r('app.integer.calendar_switch_zero'))
468    }
469    .width($r('app.string.calendar_switch_full_size'))
470    .height($r('app.string.calendar_switch_full_size'))
471    .padding(PADDING)
472  }
473}