DEV Community

xiaoyaolihua
xiaoyaolihua

Posted on • Edited on

HarmonyOS NEXT- project actual combat calendar, novice tutorial 3


In the previous article, we have finished displaying the calendar on the interface, so let's start.

  1. 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')
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

(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. )

 
Image description

  1. 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')
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

(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. )

Image description

Top comments (0)