In the previous article, we have finished displaying the calendar on the interface, so let's start.
- Calendar, calendar color style, size and other styles improved.
The sample code is as follows (including explanation and comments):
import { i18n } from '@kit.LocalizationKit';
interface CalenderType {
Calendar?: string;
Lunar?: string;
Color?: Color | string // background color
index?: number //Determine whether you can click or not, which is the number of the month
fontColor?: string | Color // font color
}
@Entry
@Component
struct Index {
private scrollerForScroll: Scroller = new Scroller();
private scrollerForList: Scroller = new Scroller();
@State listPosition: number = 0; // 0 means scrolling to the top of the List, 1 means intermediate value, and 2 means scrolling to the bottom of the List.
//Design lunar calendar time
getLunarDate(year: number, month: number, day: number) {
// 1. Set Gregorian calendar date
const gregorianCalendar = i18n.getCalendar("zh-Hans", "gregory");
gregorianCalendar.set(year, month - 1, day); // Months need to be minus 1
// 2. Convert to lunar calendar
const lunarCalendar = i18n.getCalendar("zh-Hans", "chinese");
lunarCalendar.setTime(gregorianCalendar.getTimeInMillis());
// 3. Get the lunar calendar value
const lunarYear = lunarCalendar.get("year");
const lunarMonth = lunarCalendar.get("month");
const lunarDay = lunarCalendar.get("date");
// 4. Convert to Chinese lunar calendar
const lunarMonths = ['New Year', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'Winter Moon', 'Wax Moon'];
const lunarDays = ['Lunar 1st', '2nd Lunar Month', '3rd Lunar Synchronous', '4th Lunar Synchronous', '5th Lunar Attainment', 'Lunar 6', 'Lunar 7', 'Lunar 8', 'Lunar 9', 'Lunar 10',
'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen', 'Twenty',
'Twenty-one', 'Twenty-two', 'Twenty-three', 'Twenty-four', 'Twenty-five', 'Twenty-six', 'Twenty-Seven', 'Twenty-Eight', 'Twenty-Nine', 'Thirty'];
return lunarDays[lunarDay - 1]
}
getTime(index: number = 0) {
let arr: CalenderType[] = []
const date = new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1 + index;
const day = date.getDate();
const week = date.getDay();
// Example: Get the day of the week on Thursday, June 5, 2025
const targetDate = new Date(year, month - 1, 1); // Note: Months are counted from 0 (5 for June)
const dayIndex = targetDate.getDay(); // Back 4 (Thursday)
const weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"
, "Friday", "Saturday"];
for (let i = 0; i < dayIndex; i++) {
arr.push({ Color: Color.Transparent, index: -1 })
}
// Get how many days there are in a month
const days = new Date(year, month, 0).getDate();
for (let i = 1; i < days + 1; i++) {
if ((dayIndex + i - 1) % 7 == 0 || (dayIndex + i - 1) % 7 == 6) {
arr.push({
Calendar: i + '',
Lunar: this.getLunarDate(year, month, i),
Color: Color.White,
index: i,
fontColor: '#FF4A24'
})
} else {
arr.push({
Calendar: i + '',
Lunar: this.getLunarDate(year, month, i),
Color: Color.White,
index: i,
fontColor: '#000000'
})
}
}
return arr
}
aboutToAppear(): void {
this.getTime()
this.getLunarDate(2025, 6, 7)
}
build() {
Column() {
// Nav head navigation bar
Row() {
Image($r('sys.media.ohos_ic_public_arrow_left'))
.width(28)
.aspectRatio(1)
.fillColor(Color.Black)
.onClick(() => {
})
Text('Calendar')
.fontSize(18)
Column()
.width(28)
}
.padding(8)
.width('100%')
.backgroundColor('#50cccccc')
.justifyContent(FlexAlign.SpaceBetween)
// Date arrangement
Row() {
Text('Sunday')
.fontColor('#FF4A24')
.layoutWeight(1)
.fontSize(12)
Text('Monday')
.layoutWeight(1)
.fontSize(12)
Text('Tuesday')
.layoutWeight(1)
.fontSize(12)
Text('Wednesday')
.layoutWeight(1)
.fontSize(12)
Text('Thursday')
.fontSize(12)
.layoutWeight(1)
Text('Friday')
.fontSize(12)
.layoutWeight(1)
Text('Saturday')
.fontSize(12)
.layoutWeight(1)
.fontColor('#FF4A24')
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.padding({ top: 15, bottom: 15 })
//Scroll through the calendar
Scroll(this.scrollerForScroll) {
Column() {
CalenderComp({ arr: this.getTime(), index: 0 })
CalenderComp({ arr: this.getTime(1), index: 1 })
CalenderComp({ arr: this.getTime(2), index: 2 })
}
}
.width("100%")
.layoutWeight(1)
}
.width('100%')
.layoutWeight(1)
}
}
@Component
struct CalenderComp {
@Prop arr: CalenderType[] = []
@Prop index: number = 0
year: number = 0
month: number = 0
aboutToAppear(): void {
this.year = (new Date()).getFullYear();
this.month = (new Date()).getMonth() + 1 + this.index
}
build() {
Column({ space: 5 }) {
Text(this.year + 'year' + this.month + 'month')
.fontSize(18)
.width('100%')
.padding(8)
.textAlign(TextAlign.Center)
// dividingLine
Divider()
.color(Color.Red)
.strokeWidth(2)
.margin({ top: 1, bottom: 10 })
Grid() {
ForEach(this.arr, (item: CalenderType) => {
GridItem() {
Column() {
Text(item.Calendar)
.fontSize(16)
.textAlign(TextAlign.Center)
.fontColor(item.fontColor)
Text(item.Lunar)
.fontSize(16)
.textAlign(TextAlign.Center)
.fontColor(item.fontColor)
}
// .margin(15)
}
.layoutWeight(1)
.aspectRatio(1) //'#9DC3E6'
.backgroundColor(item.Color)
})
}
.rowsGap(10)
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
}
}
}
(Note: This time the focus is on enhancing the visual presentation and interactive foundation of the calendar, and the core optimization includes four aspects: first, the CalenderType interface is extended to add style control fields - Color (background color), fontColor (text color) and index (date index); Secondly, the style is dynamically set in the date generation logic: the text of the weekend date (Saturday/Sunday) is set to red (#FF4A24), the weekday is black, the background of the blank date is transparent and marked as an invalid index (index=-1), and the valid date is unified with a white background. Third, optimize the layout of calendar grids - add aspectRatio(1) to force the square ratio, set layoutWeight(1) to achieve monospaced distribution, increase rowsGap(10) line spacing, and dynamically apply text colors; Finally, adjust the vertical spacing of the week title to 15 units, while keeping the month title and navigation bar style unchanged. The current implementation gives the calendar grid a neat square layout, with weekend dates prominently displayed in red, laying the foundation for subsequent date selection and event marking. )
- Expand the single-point calendar (store the click data of the calendar and display the effect).
import { i18n } from '@kit.LocalizationKit';
import { router } from '@kit.ArkUI';
interface CalenderType {
Calendar: string| null;
Lunar: string| null;
Color: Color | ResourceStr| null // background color
index: number| null //Determine whether you can click or not, which is the number of the month
fontColor: string | Color| null // font color
Time: Date | null
}
@ObservedV2
class CalenderTypeModel implements CalenderType {
@Trace Calendar: string | null = null
@Trace Lunar: string | null = null
@Trace Color: Color | ResourceStr | null = null
@Trace index: number | null = null
@Trace fontColor: string | Color | null = null
@Trace Time: Date | null = null
constructor(model: CalenderType) {
this.Calendar = model.Calendar
this.Lunar = model.Lunar
this.Color = model.Color
this.index = model.index
this.fontColor = model.fontColor
this.Time = model.Time
}
}
@Entry
@ComponentV2
struct Index {
private scrollerForScroll: Scroller = new Scroller();
@Local calenderArr: CalenderType[][]=[]
@Local listPosition: number = 0; // 0 means scrolling to the top of the List, 1 means intermediate value, and 2 means scrolling to the bottom of the List.
//Design lunar calendar time
/** Calendar mode 0 for single-point calendar, 1 for dotted calendar, 2 for multi-select calendar */
@Local calendarMode: number = 0; //Calendar mode 0 means a single-point calendar, 1 means a dotted calendar, and 2 means a multi-select calendar
getDate(year: number, month: number, day: number) {
// 1. Set Gregorian calendar date
const gregorianCalendar = i18n.getCalendar("zh-Hans", "gregory");
gregorianCalendar.set(year, month - 1, day); // Months need to be minus 1
// 2. Convert to lunar calendar
const lunarCalendar = i18n.getCalendar("zh-Hans", "chinese");
lunarCalendar.setTime(gregorianCalendar.getTimeInMillis());
// 3. Get the lunar calendar value
const lunarDay = lunarCalendar.get("date");
// 4. Convert to Chinese lunar calendar
const lunarDays = ['Lunar 1st', '2nd Lunar Month', '3rd Lunar Synchronous', '4th Lunar Synchronous', '5th Lunar Attainment', 'Lunar 6', 'Lunar 7', 'Lunar 8', 'Lunar 9', 'Lunar 10',
'Eleven', 'Twelve', 'Thirteen', 'Fourteen', 'Fifteen', 'Sixteen', 'Seventeen', 'Eighteen', 'Nineteen', 'Twenty',
'Twenty-one', 'Twenty-two', 'Twenty-three', 'Twenty-four', 'Twenty-five', 'Twenty-six', 'Twenty-Seven', 'Twenty-Eight', 'Twenty-Nine', 'Thirty'];
return lunarDays[lunarDay - 1]
}
getTime(index: number = 0) {
let arr: CalenderType[] = []
const date = new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1 + index;
// Example: Get the day of the week on Thursday, June 5, 2025
const targetDate = new Date(year, month - 1, 1); // Note: Months are counted from 0 (5 for June)
const dayIndex = targetDate.getDay(); // Back 4 (Thursday)
for (let i = 0; i < dayIndex; i++) {
arr.push(({
Color: Color.Transparent,
index: -1,
Calendar: null,
Lunar: null,
fontColor: null,
Time: null
}))
}
// Get how many days there are in a month
const days = new Date(year, month, 0).getDate();
for (let i = 1; i < days + 1; i++) {
if ((dayIndex + i - 1) % 7 == 0 || (dayIndex + i - 1) % 7 == 6) {
arr.push(({
Calendar: i + '',
Lunar: this.getDate(year, month, i),
Color: Color.White,
index: i,
fontColor: '#FF4A24',
Time: new Date(year, month - 1, i)
}))
} else {
arr.push(({
Calendar: i + '',
Lunar: this.getDate(year, month, i),
Color: Color.White,
index: i,
fontColor: '#000000',
Time: new Date(year, month - 1, i)
}))
}
}
return arr
}
aboutToAppear(): void {
this.calenderArr.push(this.getTime(0))
this.calenderArr.push(this.getTime(1))
this.calenderArr.push(this.getTime(2))
const param=router.getParams()
if (param === 'A la carte calendar') {
this.calendarMode = 0
}
}
build() {
Column() {
// Nav head navigation bar
Row() {
Image($r('sys.media.ohos_ic_public_arrow_left'))
.width(28)
.aspectRatio(1)
.fillColor(Color.Black)
.onClick(() => {
})
Text('Calendar')
.fontSize(18)
Column()
.width(28)
}
.padding(8)
.width('100%')
.backgroundColor('#50cccccc')
.justifyContent(FlexAlign.SpaceBetween)
// Date arrangement
Row() {
Text('Sunday')
.fontColor('#FF4A24')
.layoutWeight(1)
.fontSize(12)
Text('Monday')
.layoutWeight(1)
.fontSize(12)
Text('Tuesday')
.layoutWeight(1)
.fontSize(12)
Text('Wednesday')
.layoutWeight(1)
.fontSize(12)
Text('Thursday')
.fontSize(12)
.layoutWeight(1)
Text('Friday')
.fontSize(12)
.layoutWeight(1)
Text('Saturday')
.fontSize(12)
.layoutWeight(1)
.fontColor('#FF4A24')
}
.width('100%')
.justifyContent(FlexAlign.SpaceAround)
.padding({ top: 15, bottom: 15 })
//Scroll through the calendar
Scroll(this.scrollerForScroll) {
Column() {
ForEach(this.calenderArr, (item:CalenderType[], index) => {
CalenderItem({ arr: item, index: index, calendarMode: this.calendarMode })
})
}
}
.width("100%")
.layoutWeight(1)
}
.width('100%')
.layoutWeight(1)
}
}
@ComponentV2
struct CalenderItem {
@Local colorIndex: number = 999
@Param calendarMode: number = 0;
@Param arr: CalenderType[] = []
@Param index: number = 0
year: number = 0
month: number = 0
aboutToAppear(): void {
this.year = (new Date()).getFullYear();
this.month = (new Date()).getMonth() + 1 + this.index
}
build() {
Column({ space: 5 }) {
Text(this.year + 'year' + this.month + 'month')
.fontSize(18)
.width('100%')
.padding(8)
.textAlign(TextAlign.Center)
// dividingLine
Divider()
.color(Color.Red)
.strokeWidth(2)
.margin({ top: 1, bottom: 10 })
Grid() {
ForEach(this.arr, (item: CalenderType, index: number) => {
GridItem() {
Column() {
Text(item.Calendar)
.fontSize(16)
.textAlign(TextAlign.Center)
.fontColor(item.fontColor)
Text(item.Lunar)
.fontSize(16)
.textAlign(TextAlign.Center)
.fontColor(item.fontColor)
}
// .margin(15)
}
.layoutWeight(1)
.aspectRatio(1) //'#9DC3E6'
.backgroundColor(item.Color)
.onClick(() => {
if (item.index == -1) {
return
}
if (this.calendarMode == 0) {
item.fontColor = Color.White
item.Color = '#ff2578cf'
this.arr[index] = new CalenderTypeModel(item)
// Deposit a single point of data
AppStorage.setOrCreate<Date>('', item.Time)
return
}
})
})
}
.rowsGap(10)
.columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
}
}
}
(Explanation: This time, the single-point selection function of the calendar has been implemented and a comprehensive state management upgrade has been carried out, and the core optimization includes five aspects: first, the introduction of advanced features of ArkUI (@ObservedV2/@ComponentV2) to reconstruct state management, and the creation of observable CalenderTypeModel classes to achieve fine-grained updates; Secondly, the calendar mode switch (calendarMode) is added, which currently implements the single-point selection function of mode 0 - when you click on the date, the background turns blue (#ff2578cf), the text turns white, and the new CalenderTypeModel() triggers a responsive update; Third, integrated routing parameter processing (router.getParams()) supports automatic setting of the mode according to the "single point calendar" parameter; Fourth, enhance the date data structure - add a Time field to store the full date object, and set the blank date to null; Finally, the rendering logic is refactored: the parent component uses ForEach to iterate through the two-dimensional array calenderArr, and the child component CalenderItem receives the mode parameters and implements click interaction, and the expiration date is automatically stored in AppStorage after clicking. The current implementation lays the foundation for the subsequent expansion of the dotted/multi-select mode, while maintaining visual characteristics such as the weekend red logo and the square grid layout. )
Top comments (0)