HarmonyOS 鸿蒙Next 第9篇:自定义可左右滑动的日历控件

发布于 1周前 作者 bupafengyu 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 第9篇:自定义可左右滑动的日历控件 第9篇:自定义可左右滑动的日历控件

效果图如下

https://alliance-communityfile-drcn.dbankcdn.com/FileServer/getFile/cmtybbs/184/323/657/0070086000184323657.20241213094255.39620227619261132187796175144585:50001231000000:2800:5C9B21D19FA758C81772779607D29D2C8578C226FDFA6EB2018E8A70FADCC869.gif

1. 关键点

  • 使用Swiper加载 3 个月的日历,即showDates数据。
  • 初始显示中间一条,即.index(1),并且循环显示.loop(true)。
  • 滑动结束后(即.onChange),滑动后显示的数据不变(否则会重新刷新 UI),其左右重新计算上个月和下个月,见changeCalender。

看代码:

@Local showDates: Date[] = [dateHelper.lastMoth(), new Date(), dateHelper.nextMoth()]; //上月/今月/下月
@Local selectData: Date = new Date(); //当前选中的日期
private showIndex: number = 1; //默认显示的swipe 下标

Swiper() {
    ForEach(this.showDates, (item: Date) => {
      this.monthView(item)
    })
  }
  .index(1)
  .loop(true)
  .indicator(false) //关闭指示器
  .onChange((index: number) => { //不显示下表
    let cur = this.showDates[index];
    this.showIndex = index;
    this.changeCalender(cur);
  })

private changeCalender(newDate: Date) {
    this.showDates[this.showIndex] = newDate;
    if (this.showIndex === 0) {
        this.showDates[1] = dateHelper.nextMoth(newDate);
        this.showDates[2] = dateHelper.lastMoth(newDate);
    } else if (this.showIndex === 1) {
        this.showDates[0] = dateHelper.lastMoth(newDate);
        this.showDates[2] = dateHelper.nextMoth(newDate);
    } else { //this.showIndex === 2
        this.showDates[1] = dateHelper.lastMoth(newDate);
        this.showDates[0] = dateHelper.nextMoth(newDate);
    }
    this.onSelected?.(newDate, this.selectData);
    this.selectData = newDate;
}

2. 完整代码

import { dateHelper } from './DateHelper';

@ComponentV2
export struct CalendarView {
  @Event onSelected?: (newDate: Date, oldDate?: Date) => void; //点击时发生改变
  @Local showDates: Date[] = [dateHelper.lastMoth(), new Date(), dateHelper.nextMoth()]; //上月/今月/下月
  @Local selectData: Date = new Date(); //当前选中的日期
  private showIndex: number = 1; //默认显示的swipe 下标

  aboutToAppear(): void {
    this.onSelected?.(this.selectData);
  }

  build() {
    Column({ space: 10 }) {
      // 年月选择部分
      RelativeContainer() {
        Image($r("app.media.ic_back"))
          .height(40)
          .padding(10)
          .aspectRatio(1)
          .id('last')
          .alignRules({ left: { 'anchor': '__container__', 'align': HorizontalAlign.Start } })
          .onClick(() => {
            this.changeCalender(dateHelper.lastMoth(this.selectData));
          });

        Row() {
          Text(`${this.selectData.getFullYear()}年${this.selectData.getMonth() + 1}月`)
            .fontSize(18)
            .textAlign(TextAlign.Center)
          Image($r("app.media.ic_down"))
            .width(15)
            .aspectRatio(1)
        }
        .id('select')
        .alignRules({
          middle: { 'anchor': '__container__', 'align': HorizontalAlign.Center },
          center: { 'anchor': '__container__', 'align': VerticalAlign.Center }
        })
        .onClick(() => {
          this.getUIContext().showDatePickerDialog({
            selected: this.selectData,
            onDateAccept: (value: Date) => {
              this.changeCalender(value);
            },
          })
        });

        if (!this.isCurMonth()) { //不是本月才显示跳回去本月
          Text('now')
            .fontSize(12)
            .fontColor(Color.White)
            .backgroundColor($r('app.color.theme_2'))
            .borderRadius(2)
            .padding({ left: 5, right: 5 })
            .margin({ left: 4 })
            .id('now')
            .alignRules({
              left: { 'anchor': 'select', 'align': HorizontalAlign.End },
              center: { 'anchor': '__container__', 'align': VerticalAlign.Center }
            })
            .onClick(() => {
              this.changeCalender(new Date());
            });
        }

        Image($r("app.media.ic_arrow"))
          .width(40)
          .aspectRatio(1)
          .padding(10)
          .id('next')
          .alignRules({ right: { 'anchor': '__container__', 'align': HorizontalAlign.End } })
          .onClick(() => {
            this.changeCalender(dateHelper.nextMoth(this.selectData));
          });
      }
      .height(40)
      .width('100%')


      // 日历表头
      Row() {
        ForEach(["日", "一", "二", "三", "四", "五", "六"], (day: string) => {
          Text(day)
            .width('14%')
            .fontColor('#333333')
            .fontSize(10)
            .textAlign(TextAlign.Center);
        });
      }
      .margin({ bottom: 12 })
      .justifyContent(FlexAlign.SpaceBetween)

      // 日历内容
      Swiper() {
        ForEach(this.showDates, (item: Date) => {
          this.monthView(item)
        })
      }
      .width('100%')
      .height('auto')
      .cachedCount(3)
      .index(1)
      .autoPlay(false)
      .loop(true)
      .itemSpace(0)
      .vertical(false)
      .displayArrow(false, false)
      .indicator(false) //关闭指示器
      .onChange((index: number) => { //不显示下表
        let cur = this.showDates[index];
        this.showIndex = index;
        this.changeCalender(cur);
      })
    }
    .padding({ left: '1%', right: '1%' }) // 1% + 14%x7 + 1% = 100%
  }

  private changeCalender(newDate: Date) {
    this.showDates[this.showIndex] = newDate;
    if (this.showIndex === 0) {
      this.showDates[1] = dateHelper.nextMoth(newDate);
      this.showDates[2] = dateHelper.lastMoth(newDate);
    } else if (this.showIndex === 1) {
      this.showDates[0] = dateHelper.lastMoth(newDate);
      this.showDates[2] = dateHelper.nextMoth(newDate);
    } else { //this.showIndex === 2
      this.showDates[1] = dateHelper.lastMoth(newDate);
      this.showDates[0] = dateHelper.nextMoth(newDate);
    }
    this.onSelected?.(newDate, this.selectData);
    this.selectData = newDate;
  }

  @Builder
  monthView(date: Date) {
    Column({ space: 5 }) {
      ForEach(dateHelper.buildCalendar(date), (week: number[]) => {
        Row() {
          ForEach(week, (day: number) => {
            Column() {
              Text(`${day}`)// 空字符串用于补位
                .width('14%')
                .fontColor('#333333')
                .fontSize(13)
                .textAlign(TextAlign.Center)
                .margin({ top: 5 })
            }
            .height('100%')
            .backgroundColor(this.selectDayColor(date, day))
            .borderRadius(3)
            .onClick(() => {
              const newData = new Date(date.getFullYear(), date.getMonth(), day);
              this.changeCalender(newData);
            })
          })
        }
        .height(50)
        .justifyContent(FlexAlign.SpaceBetween)
      });
    }
  }

  isCurMonth(): boolean {
    const cur = new Date();
    if (cur.getFullYear() !== this.selectData.getFullYear()) {
      return false;
    }
    if (cur.getMonth() !== this.selectData.getMonth()) {
      return false;
    }
    return true;
  }

  selectDayColor(date: Date, day: number) {
    return dateHelper.isOneDay(this.selectData, new Date(date.getFullYear(), date.getMonth(), day))
      ? '#ff8cdaf1' : Color.Transparent
  }
}

export namespace dateHelper {

    /**
     * 生成该月的月历,日历是一个二维数组,每一行代表一周。
     * 如:周日 ~ 周六
     [
     ["", "", "", "", "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"]
     ]
     */
    export function buildCalendar(date: Date): number[][] {
        //首先计算出该月的第一天是星期几,从 0(周日) ~ 6(周六)
        const firstDay = new Date(date.getFullYear(), date.getMonth(), 1).getDay();
        // //然后计算该月的总天数
        const days = new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate();
        const calendar: number[][] = [];
        let week: number[] = Array(firstDay).fill(''); // 填充空白

        for (let day = 1; day <= days; day++) {
            week.push(day);
            if (week.length === 7) {
                calendar.push(week);
                week = [];
            }
        }
        if (week.length > 0) {
            calendar.push([...week, ...Array(7 - week.length).fill('')]); // 填充末尾空白
        }
        return calendar;
    }

    /**
     * 判断是否是同一天
     */
    export function isOneDay(date1: Date, date2: Date): boolean {
        if (date1.getFullYear() !== date2.getFullYear()) {
            return false;
        }
        if (date1.getMonth() !== date2.getMonth()) {
            return false;
        }
        if (date1.getDate() !== date2.getDate()) {
            return false;
        }
        return true;
    }

    /**
     * 获取上一个月(1号)
     */
    export function lastMoth(date: Date = new Date()): Date {
        return new Date(date.getFullYear(), date.getMonth() - 1, 1)
    }

    /**
     * 获取下一个月(1号)
     */
    export function nextMoth(date: Date = new Date()): Date {
        return new Date(date.getFullYear(), date.getMonth() + 1, 1)
    }
}

到这里,所有的内容已经结束了!本章的完整源码已经上传到gitee了:https://gitee.com/qincji/ZeroOneApp


更多关于HarmonyOS 鸿蒙Next 第9篇:自定义可左右滑动的日历控件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

1 回复

更多关于HarmonyOS 鸿蒙Next 第9篇:自定义可左右滑动的日历控件的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


针对帖子标题“HarmonyOS 鸿蒙Next 第9篇:自定义可左右滑动的日历控件”的问题,以下回答专注于鸿蒙系统(HarmonyOS)相关的技术内容:

在HarmonyOS中,自定义可左右滑动的日历控件通常涉及UI组件的自定义与事件处理。你可以利用ArkUI(eTS或JS框架)来实现这一功能。具体步骤如下:

  1. 定义组件:使用ArkUI的组件定义语言,创建一个自定义的日历组件。该组件应包含显示日期的视图以及用于滑动的容器。

  2. 实现滑动逻辑:利用ArkUI提供的滑动事件监听机制,为日历组件添加左右滑动的事件处理。这通常涉及监听滑动开始、进行中、结束等事件,并据此更新日历的显示内容。

  3. 日期更新:根据滑动的方向,更新日历组件中显示的日期。这可能需要维护一个当前日期的状态,并在滑动事件触发时更新该状态。

  4. 样式与布局:为日历组件设计合适的样式与布局,以确保其具有良好的用户体验。

请注意,实现过程中可能需要深入了解ArkUI的组件库、事件机制以及状态管理。若在实现过程中遇到具体问题,建议查阅HarmonyOS的官方文档或相关开发资源。

如果问题依旧没法解决请联系官网客服,官网地址是:https://www.itying.com/category-93-b0.html

回到顶部