HarmonyOS Next 办公应用:可变日历组件的开发实现
概述
在 HarmonyOS Next 办公类应用开发中,实现一个灵活且功能丰富的日历组件是常见需求。下面将介绍如何构建一个可变日历组件,支持日期选择、月份切换等功能。
核心代码功能及对应代码段
1. 日期数据类定义
定义了 LunarDate 和 CJDateItem 类,用于处理阴历日期和日期项。
@Observed
class LunarDate {
public year?: number
public month?: number
public day?: number
public isLeapMonth?: boolean
}
@Observed
export class CJDateItem {
fullYear: number = 0
month: number = 0
date: number = 0
week?: number
time?: number
lunarDate?: LunarDate
isPre?: boolean
isNext?: boolean
isToday?: boolean
constructor(date: Date, isPre?: boolean, isNext?: boolean) {
if (date) {
this.fullYear = date.getFullYear()
this.month = date.getMonth()
this.date = date.getDate()
this.week = date.getDay()
this.time = date.getTime()
}
this.isPre = isPre
this.isNext = isNext
}
// 日期比较方法
equalDay(other: CJDateItem | number): boolean {... }
compareDay(other: CJDateItem | number): number {... }
bigThan(other: CJDateItem | number, hasEqual: boolean = false) {... }
smallThan(other: CJDateItem | number, hasEqual: boolean = false) {... }
}
2. 可变日历组件
VariableCalendar 组件是核心组件,负责日历的渲染和交互。
@Entry
@Component
export struct VariableCalendar {
@State @Watch('WatchTitle') title: string = ''
@State hasPre: boolean = false
@State hasNext: boolean = false
@State @Watch('WatchSelectItem') selectItem: CJDateItem = new CJDateItem(new Date())
// 其他状态变量...
aboutToAppear() {
this.initAttr()
if (!this.selectItem) {
let temp = new CJDateItem(this.today)
this.selectItem = temp
}
this.calcDatas()
}
initAttr() {
// 初始化日期范围
this.today = new Date(
this.today.getFullYear(),
this.today.getMonth(),
this.today.getDate(),
)
if (!this.startDate) {
this.startDate = new Date(1970, 0, 1)
} else {
this.startDate = new Date(this.startDate.getFullYear(), this.startDate.getMonth(), this.startDate.getDate())
}
if (!this.endDate) {
this.endDate = new Date(this.today.getFullYear() + 10, 11, 31)
} else {
this.endDate = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate())
}
}
preMonth() {
// 切换到上一个月
this.dates = []
this.startDay.setMonth(this.startDay.getMonth() - 1)
this.calcDatas()
}
nextMonth() {
// 切换到下一个月
this.dates = []
this.startDay.setMonth(this.startDay.getMonth() + 1)
this.calcDatas()
}
calcDatas() {
// 计算当前月份的日期数据
const startDay = this.startDay
this.currMonth = startDay.getMonth()
this.currYear = startDay.getFullYear()
this.title = `${startDay.getFullYear()}年${startDay.getMonth() + 1}月`
startDay.setDate(1)
// 判断是否有上一个月和下一个月
this.hasPre = startDay.getFullYear() > this.startDate.getFullYear() ||
(startDay.getFullYear() === this.startDate.getFullYear() && startDay.getMonth() > this.startDate.getMonth())
this.hasNext = startDay.getFullYear() < this.endDate.getFullYear() ||
(startDay.getFullYear() === this.endDate.getFullYear() && startDay.getMonth() < this.endDate.getMonth())
// 计算日期数据
let endDay: Date = new Date(
startDay.getFullYear(),
startDay.getMonth() + 1,
0, 23, 59, 59)
let tempDate: Date = new Date(
startDay.getFullYear(),
startDay.getMonth(),
startDay.getDate()
)
const count = endDay.getDate()
const preCount = startDay.getDay()
const nextCount = 6 - endDay.getDay()
const finilCount = count + preCount + nextCount
tempDate.setDate(tempDate.getDate() - preCount)
for (let index = 0; index < finilCount; index++) {
let item = new CJDateItem(
tempDate,
(index < preCount ? true : false) || this.startDate.getTime() > tempDate.getTime(),
(index >= preCount + count ? true : false) || this.endDate.getTime() < tempDate.getTime()
)
this.dates.push(item)
tempDate.setDate(tempDate.getDate() + 1)
}
}
@Builder
CusTopLayout() {
// 日历顶部布局,包含月份切换按钮和标题
Row() {
Column() {
Image($r('sys.media.ohos_ic_public_arrow_left'))
.width(28)
.fillColor(this.hasPre ? '#252a34' : '#9E9E9E')
.aspectRatio(1)
}
.justifyContent(FlexAlign.Center)
.height('100%')
.aspectRatio(1)
.onClick(() => {
if (this.hasPre) {
this.preMonth()
}
})
Blank()
Row() {
Text(this.title)
.fontSize(18)
.fontColor('#252a34')
}
Blank()
Column() {
Image($r('sys.media.ohos_ic_public_arrow_right'))
.fillColor(this.hasNext ? '#252a34' : '#9E9E9E')
.width(28)
.aspectRatio(1)
}
.justifyContent(FlexAlign.Center)
.height('100%')
.aspectRatio(1)
.onClick(() => {
if (this.hasNext) {
this.nextMonth()
}
})
}
.width('100%')
.height(50)
}
build() {
Column() {
Column() {
this.CusTopLayout()
Row() {
ForEach(this.weeks, (item: string, index) => {
Text(item)
.textAlign(TextAlign.Center)
.fontColor('#9E9E9E')
.fontSize(13)
.layoutWeight(1)
})
}
.height(40)
}
.zIndex(99)
.backgroundColor(Color.White)
Column() {
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.dates, (item: CJDateItem, index: number) => {
CalenderCell({
item: item,
today: this.today.getTime(),
selectItem: $selectItem,
hasPre: this.hasPre,
hasNext: this.hasNext,
cellClick: (item: CJDateItem) => {
if (item.isPre) {
this.preMonth()
} else if (item.isNext) {
this.nextMonth()
}
}
})
.width(`14.28%`)
.onAreaChange((oldValue: Area, newValue: Area) => {
if (item.equalDay(this.selectItem)) {
this.currentYOffset = Number(newValue.position.y);
}
})
}, (item: CJDateItem) => JSON.stringify(item))
}
.width('100%')
.height(this.calendarHeight)
.offset({ x: this.offsetX, y: this.offsetY })
.backgroundColor(Color.White)
.parallelGesture(
PanGesture(this.panOption)
.onActionUpdate((event: GestureEvent) => {
if (event) {
this.offsetX = event.offsetX;
}
})
.onActionEnd((event: GestureEvent) => {
if (event) {
animateTo({
curve: Curve.Smooth
}, () => {
if (Number(event.offsetX) > 160) {
this.preMonth();
this.offsetX = 0;
} else {
this.offsetX = 0;
}
if (event.offsetX < -160) {
this.nextMonth();
this.offsetX = 0;
} else {
this.offsetX = 0;
}
})
}
})
)
}
.zIndex(1)
}
.width('100%')
}
}