【FFH】鸿蒙应用开发实例——日历 原创

唔哩哇啦
发布于 2024-10-26 17:57
浏览
1收藏

目录

​前言​

​一.定义变量​

​二.编辑日历页面 ​

​1.框架​

​2.编写页面标题​

​3.搜索栏及跳转按钮​

​1)搜索框​

​2)搜索按钮​

​3)设置跳转按钮​

​4.日历主体布局 ​

​5.日历空格函数​

​6.日历布局函数​

​三.源代码​


前言

本文是记录笔者进行鸿蒙北向学习时,用ArkTs编写一个可以自适应屏幕的简易日历的开发过程。(真的非常简易!)源代码附在文章末尾,如有不足之处欢迎一起讨论~

成品->

【FFH】鸿蒙应用开发实例——日历-鸿蒙开发者社区

右上角输入年月,按下“go”按钮即可跳转至指定年月;按‘<’或‘>’即可切换至上个月或下个月。

下面是相关代码及开发过程 ^~^


一.定义变量

  • 使用riqi数组储存页面显示的日期,没有日期的几天使用空格占位。
  • 用week数组存储星期,用于后续设置星期栏
  • 定义动态变量年year月month日day(以2024.12作为初始页面)

@Entry
@Component
struct DataPage {
  @State riqi:Array<string>=["1","2","3","4","5","6","7","8",
    "9","10","11","12","13","14","15",
    "16","17","18","19","20","21","22",
    "23","24","25","26","27","28","29","30","31"," "," "," "," "];
  @State week:Array<string>=["日","一","二","三","四","五","六"]
  @State year:number=2024;
  @State month:number=12;
  @State day:number=0;
  //以2024.12的日历作为初始页面
  //对各个变量进行初始化


二.编辑日历页面 

主页面主要是使用Grid组件进行页面布局,使用GridItem组件对页面进行分块。(注意build下只能有一个Grid,GridItem下也只能有一个子组件,但是Grid下可以有多个GridItem,并且Grid的子组件必须是GridItem组件

1.框架

build() {
    Grid() {
    ...............
    }
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
    .rowsTemplate(this.riqi.length>35?'1fr 1fr 2fr 2fr 2fr 2fr 2fr 2fr 2fr 1fr':'1fr 1fr 2fr 2fr 2fr 2fr 2fr 2fr 1fr' )
    //根据月份天数判断需要几行


(接下来的代码均在Grid()下编写)

columntemplate及rowstemplate为设置页面分块及占比的相关属性,官方文档见​Grid组件​

2.编写页面标题

标题要随着年月的改变相应的改变,因此我们需要在text中引用变量,使其显示的年月能随着变量的改变而改变。

(columnstart,columnend以及之后用到的rowstart,rowend均为设置GridItem容器大小的相关属性。有需要的可以去看这个​​GridItem组件​​官方文档)

GridItem(){
        Text(`${this.year}年${this.month}月`)
          .fontSize(25)
          .fontWeight(700)
        //根据用户输入的年月更改页面标题
      }.columnStart(1)
      .columnEnd(3)


3.搜索栏及跳转按钮

1)搜索框

接下来,我们需要使用TextInput组件设置输入框,并使用onChange事件将状态变量与文本实时绑定,以便更新日历标题

GridItem(){
        TextInput({placeholder:'年'})
          .onChange((value: string) => {
            this.year=Number(value);

          })
          .fontColor("#ff034980")
      }.columnStart(4)
      .columnEnd(5)
      GridItem(){
        TextInput({placeholder:'月'})
          .onChange((value: string) => {
            this.month=Number(value);

          })
          .fontColor("#ff034980")
      }.columnStart(6)
      .columnEnd(6)
      //记录用户输入的年月


2)搜索按钮

使用button的onclick事件调用我们编写的查找日期的函数,foreach会根据riqi数组的变化重新渲染页面

GridItem(){
       Button({type:ButtonType.Circle}) {
          Text("go")
       }.height("30")
       .width("70")
       .backgroundColor("#a3ff8c8c")
       .onClick((event: ClickEvent) => {
         this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
       })
       //点击事件调用findday()函数找到2000.1.1与用户输入的日期相差几天,将其作为参数传入tian()函数中
       //调用tian()函数对该月份的日期进行排版,并以数组形式传给riqi数组,以便后续foreach循环渲染
      }.columnStart(7)


3)设置跳转按钮

使用跳转按钮直接前往上一月或下一月的日历,记得将1月以及12月这种特殊情况考虑在内,通过onclick事件更新相关变量后调用相关函数更新riqi数组,foreach重新渲染页面

GridItem() {
        Column() {
          Blank()
        }
      }.columnStart(1)
      .columnEnd(5)
      //使用blank进行空白占位
GridItem(){
        Button({type:ButtonType.Capsule}) {
          Text("<")
        }.height("30")
        .width("50")
        .backgroundColor("#bcf5d7ff")
        .onClick((event: ClickEvent) => {
          if(this.month==1){
            this.month=12;
            this.year--;
          }else{
            this.month--;
          }
          this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
        })
      }.columnStart(6)
      .columnEnd(6)
      
GridItem(){
        Button({type:ButtonType.Capsule}) {
          Text(">")
        }.height("30")
        .width("50")
        .backgroundColor("#bcf5d7ff")
        .onClick((event: ClickEvent) => {
          if(this.month==12){
            this.month=1;
            this.year++;
          }else{
            this.month++;
          }
          this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
        })
      }.columnStart(6)
      .columnEnd(7)
      //跳转前一个或下一个月


4.日历主体布局 

使用foreach循环渲染以减少重复代码

ForEach(this.week,(item:string)=>{
        GridItem() {
          Button({ type: ButtonType.Circle }) {
            Text(`${item}`)   //引用变量
              .fontSize(20)
              .fontColor("#ff808080")
          }.width('90%')
          .height('90%')
          .borderRadius(50)
          .backgroundColor("#fffff2c0")
        }
      })//循环渲染星期导航栏
      ForEach(this.riqi, (item:string)=> {
          GridItem() {
            Button( {type: ButtonType.Circle}) {
              Text(`${item}`)
                .fontSize(20)
            }.width('90%')
            .height('90%')
            .borderRadius(50)
            .backgroundColor(item === " " ? "#6ac8f4f8" : "#9faff8f8")
             //根据这个button有无日期填充不一样的背景颜色
          }
        })//渲染日期


5.日历空格函数

由于日历并不是全被日期占满,会有空出来的地方,就像下面那样

【FFH】鸿蒙应用开发实例——日历-鸿蒙开发者社区

因此我们需要找到日历的开头需要几个空格子,这里我们以2000.1.1为参照,计算当前年月的1号距离2000.1.1几天,然后除7取余,余数即为当前月份的日历前空格子的个数,然后返回这个余数。(注意闰年)

function findday(y:number,m:number,d:number) {
  let run: Array<number> = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let feirun: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let sum: number = 0;
  let nian: number = 2000;
  let yue: number = 1;
  let ri: number = 1;
  let jujin: number = 0;
  for (let j = nian; j < y; j++) {
    //计算前几年有多少天,需区分是否为闰年
    sum = 0;
    if ((j % 400 == 0) || (j % 4 == 0 && j % 100 != 0)) {
      sum += 366;
    } else {
      sum += 365;
    }
    jujin += sum;
  }
  sum = 0; //计算当年有多少天
  if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0)) {
    for (let i = 0; i < m-1; i++) {
      sum += run[i];
    }
  } else {
    for (let i = 0; i < m-1; i++) {
      sum += feirun[i];
    }
  }
  jujin += sum;
  jujin = (jujin+6) % 7;
  //计算出要显示的日历前面需要空几格
  return


6.日历布局函数

知道日历前空格数后,我们现在来对日历的日期进行排版,为了美观我们需要将没有日期的几天填充空格让其可以显示,就像这样

【FFH】鸿蒙应用开发实例——日历-鸿蒙开发者社区

因此我们使用3个循环对数组进行填充,然后返回数组(同样的,注意闰年) 

function tian(y:number,m:number,kong:number) {
  let day:Array<string>=new Array (35);
  for(let i=0;i<kong;i++){
    day[i]=" ";
  }//对没有日期的格子填空格
  let d:number;
  let run: Array<number> = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let feirun: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let sum: number = 0;
  if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0)) {
    d=run[m-1];
  } else {
    d = feirun[m-1];
  }//填充有日期的格子
  let a=0;
  for(let i=kong;i<d+kong;i++){
    a++;
    day[i]=a.toString();
  }
  if(day.length<=35) {
    for (let i = d + kong; i < 35; i++) {
      day[i] = " ";
    }
  }
  else {
    for (let i = d + kong; i < 42; i++) {
      day[i] = " ";
    }
  }//补全剩余空格
  return day;
  //返回day数组给foreach以便进行循环渲染

这样,我们的日历就制作完成啦 !

三.源代码

@Entry
@Component
struct DataPage {
  @State riqi:Array<string>=["1","2","3","4","5","6","7","8",
    "9","10","11","12","13","14","15",
    "16","17","18","19","20","21","22",
    "23","24","25","26","27","28","29","30","31"," "," "," "," "];
  @State week:Array<string>=["日","一","二","三","四","五","六"]
  @State year:number=2024;
  @State month:number=12;
  @State day:number=0;
  //以2024.12的日历作为初始页面
  //对各个变量进行初始化
  build() {
    Grid() {
      GridItem(){
        Text(`${this.year}年${this.month}月`)
          .fontSize(25)
          .fontWeight(700)
        //根据用户输入的年月更改页面标题
      }.columnStart(1)
      .columnEnd(3)

      GridItem(){
        TextInput({placeholder:'年'})
          .onChange((value: string) => {
            this.year=Number(value);

          })
          .fontColor("#ff034980")
      }.columnStart(4)
      .columnEnd(5)
      GridItem(){
        TextInput({placeholder:'月'})
          .onChange((value: string) => {
            this.month=Number(value);

          })
          .fontColor("#ff034980")
      }.columnStart(6)
      .columnEnd(6)
      //记录用户输入的年月
     GridItem(){
       Button({type:ButtonType.Circle}) {
          Text("go")
       }.height("30")
       .width("70")
       .backgroundColor("#a3ff8c8c")
       .onClick((event: ClickEvent) => {
         this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
       })
       //点击事件调用findday()函数找到2000.1.1与用户输入的日期相差几天,将其作为参数传入tian()函数中
       //调用tian()函数对该月份的日期进行排版,并以数组形式传给riqi数组,以便后续foreach循环渲染
      }.columnStart(7)
      GridItem() {
        Column() {
          Blank()
        }
      }.columnStart(1)
      .columnEnd(5)
      GridItem(){
        Button({type:ButtonType.Capsule}) {
          Text("<")
        }.height("30")
        .width("50")
        .backgroundColor("#bcf5d7ff")
        .onClick((event: ClickEvent) => {
          if(this.month==1){
            this.month=12;
            this.year--;
          }else{
            this.month--;
          }
          this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
        })
      }.columnStart(6)
      .columnEnd(6)
      GridItem(){
        Button({type:ButtonType.Capsule}) {
          Text(">")
        }.height("30")
        .width("50")
        .backgroundColor("#bcf5d7ff")
        .onClick((event: ClickEvent) => {
          if(this.month==12){
            this.month=1;
            this.year++;
          }else{
            this.month++;
          }
          this.riqi=tian(this.year,this.month, findday(this.year,this.month,this.day))
        })
      }.columnStart(6)
      .columnEnd(7)
      //跳转前一个或下一个月
      ForEach(this.week,(item:string)=>{
        GridItem() {
          Button({ type: ButtonType.Circle }) {
            Text(`${item}`)
              .fontSize(20)
              .fontColor("#ff808080")
          }.width('90%')
          .height('90%')
          .borderRadius(50)
          .backgroundColor("#fffff2c0")
        }
      })//循环渲染星期导航栏
      ForEach(this.riqi, (item:string)=> {
          GridItem() {
            Button( {type: ButtonType.Circle}) {
              Text(`${item}`)
                .fontSize(20)
            }.width('90%')
            .height('90%')
            .borderRadius(50)
            .backgroundColor(item === " " ? "#6ac8f4f8" : "#9faff8f8")
             //根据这个button有无日期填充不一样的背景颜色
          }
        })//渲染日期

    }
    .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
    .rowsTemplate(this.riqi.length>35?'1fr 1fr 2fr 2fr 2fr 2fr 2fr 2fr 2fr 1fr':'1fr 1fr 2fr 2fr 2fr 2fr 2fr 2fr 1fr' )
//根据月份天数判断需要几行
}
}

function findday(y:number,m:number,d:number) {
  let run: Array<number> = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let feirun: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let sum: number = 0;
  let nian: number = 2000;
  let yue: number = 1;
  let ri: number = 1;
  let jujin: number = 0;
  for (let j = nian; j < y; j++) {
    //计算前几年有多少天,需区分是否为闰年
    sum = 0;
    if ((j % 400 == 0) || (j % 4 == 0 && j % 100 != 0)) {
      sum += 366;
    } else {
      sum += 365;
    }
    jujin += sum;
  }
  sum = 0; //计算当年有多少天
  if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0)) {
    for (let i = 0; i < m-1; i++) {
      sum += run[i];
    }
  } else {
    for (let i = 0; i < m-1; i++) {
      sum += feirun[i];
    }
  }
  jujin += sum;
  jujin = (jujin+6) % 7;
  //计算出要显示的日历前面需要空几格
  return jujin;
}

function tian(y:number,m:number,kong:number) {
  let day:Array<string>=new Array (35);
  for(let i=0;i<kong;i++){
    day[i]=" ";
  }//对没有日期的格子填空格
  let d:number;
  let run: Array<number> = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let feirun: Array<number> = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  let sum: number = 0;
  if ((y % 400 == 0) || (y % 4 == 0 && y % 100 != 0)) {
    d=run[m-1];
  } else {
    d = feirun[m-1];
  }//填充有日期的格子
  let a=0;
  for(let i=kong;i<d+kong;i++){
    a++;
    day[i]=a.toString();
  }
  if(day.length<=35) {
    for (let i = d + kong; i < 35; i++) {
      day[i] = " ";
    }
  }
  else {
    for (let i = d + kong; i < 42; i++) {
      day[i] = " ";
    }
  }//补全剩余空格
  return day;
  //返回day数组给foreach以便进行循环渲染

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2024-10-26 17:57:33修改
收藏 1
回复
举报
回复
    相关推荐