在这里插入图片描述

前言

日程管理是OA办公系统中不可或缺的功能模块,它帮助员工合理安排工作时间、记录重要会议、设置任务提醒。日历组件作为日程管理的核心界面,需要直观地展示每日、每周、每月的日程安排,支持快速切换视图、添加日程、查看详情等操作。本文将深入讲解如何使用Flutter和OpenHarmony开发一个功能丰富的日程日历组件。

组件功能规划

日程日历组件需要支持三种视图模式:月视图用于查看整月日程概览,周视图用于查看一周的详细安排,日视图用于查看单日的时间线。组件还需要支持日程的增删改查操作,以及与后端服务的数据同步。在视觉设计上,有日程的日期需要有明显标识,不同类型的日程用不同颜色区分。

Flutter端实现

定义日程数据模型:

class ScheduleEvent {
  final String id;
  final String title;
  final String description;
  final DateTime startTime;
  final DateTime endTime;
  final Color color;
  final bool isAllDay;
  
  ScheduleEvent({
    required this.id,
    required this.title,
    this.description = '',
    required this.startTime,
    required this.endTime,
    required this.color,
    this.isAllDay = false,
  });
}

日程数据模型包含了标题、描述、开始结束时间、颜色标识等字段。isAllDay字段用于标识全天事件,全天事件在日历上的显示方式与普通事件不同。Color字段允许用户为不同类型的日程设置不同颜色,便于视觉区分。

日历组件的基础结构:

class CalendarWidget extends StatefulWidget {
  final List<ScheduleEvent> events;
  final Function(DateTime) onDateSelected;
  final Function(ScheduleEvent) onEventTap;
  
  const CalendarWidget({
    Key? key,
    required this.events,
    required this.onDateSelected,
    required this.onEventTap,
  }) : super(key: key);
  
  
  State<CalendarWidget> createState() => _CalendarWidgetState();
}

日历组件接收日程事件列表和两个回调函数,onDateSelected在用户选择日期时触发,onEventTap在用户点击具体日程时触发。这种设计使日历组件专注于展示和交互,具体的业务逻辑由父组件处理。

状态类中的关键变量定义:

class _CalendarWidgetState extends State<CalendarWidget> {
  DateTime _currentMonth = DateTime.now();
  DateTime? _selectedDate;
  CalendarView _currentView = CalendarView.month;
  
  List<ScheduleEvent> get _selectedDateEvents {
    if (_selectedDate == null) return [];
    return widget.events.where((e) => 
      isSameDay(e.startTime, _selectedDate!)
    ).toList();
  }
}

状态类管理当前显示的月份、选中的日期和当前视图模式。_selectedDateEvents是一个计算属性,根据选中日期过滤出当天的日程列表。使用getter而不是普通方法可以像访问属性一样使用,代码更加简洁。

月视图网格的构建:

GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 7,
    childAspectRatio: 1,
  ),
  itemCount: 42,
  itemBuilder: (context, index) {
    final date = _getDateForIndex(index);
    final hasEvents = _hasEventsOnDate(date);
    return _buildDayCell(date, hasEvents);
  },
)

月视图使用GridView构建7列的网格,每行代表一周。itemCount设为42是因为一个月视图最多需要6行(上月末尾+当月+下月开头)。childAspectRatio设为1使每个单元格为正方形,这是日历的标准布局。

日期单元格的构建方法:

Widget _buildDayCell(DateTime date, bool hasEvents) {
  final isSelected = _selectedDate != null && isSameDay(date, _selectedDate!);
  final isToday = isSameDay(date, DateTime.now());
  
  return GestureDetector(
    onTap: () {
      setState(() => _selectedDate = date);
      widget.onDateSelected(date);
    },
    child: Container(
      decoration: BoxDecoration(
        color: isSelected ? Colors.blue : null,
        shape: BoxShape.circle,
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text(
            '${date.day}',
            style: TextStyle(
              color: isSelected ? Colors.white : (isToday ? Colors.blue : null),
            ),
          ),
          if (hasEvents)
            Container(
              width: 6,
              height: 6,
              decoration: BoxDecoration(
                color: isSelected ? Colors.white : Colors.blue,
                shape: BoxShape.circle,
              ),
            ),
        ],
      ),
    ),
  );
}

日期单元格需要处理多种状态:是否选中、是否是今天、是否有日程。选中状态用蓝色圆形背景标识,今天用蓝色文字标识,有日程的日期在数字下方显示一个小圆点。这种视觉设计让用户能够快速获取日历信息。

OpenHarmony鸿蒙端实现

定义日程数据接口:

interface ScheduleEvent {
  id: string
  title: string
  description: string
  startTime: number
  endTime: number
  color: string
  isAllDay: boolean
}

鸿蒙端使用interface定义日程数据结构,时间使用时间戳格式便于计算和比较。color字段存储十六进制颜色字符串,用于在日历上区分不同类型的日程。

日历组件的基础结构:

@Component
struct CalendarWidget {
  @State currentMonth: Date = new Date()
  @State selectedDate: Date | null = null
  @Prop events: ScheduleEvent[] = []
  private onDateSelect: (date: Date) => void = () => {}
}

组件使用@State管理当前月份和选中日期,@Prop接收外部传入的日程列表。Date类型用于日期操作,鸿蒙的ArkTS完全支持JavaScript的Date对象。

月份切换头部的构建:

@Builder
CalendarHeader() {
  Row() {
    Image($r('app.media.arrow_left'))
      .width(24)
      .height(24)
      .onClick(() => this.previousMonth())
    
    Text(this.formatMonth(this.currentMonth))
      .fontSize(18)
      .fontWeight(FontWeight.Medium)
      .margin({ left: 16, right: 16 })
    
    Image($r('app.media.arrow_right'))
      .width(24)
      .height(24)
      .onClick(() => this.nextMonth())
  }
  .width('100%')
  .justifyContent(FlexAlign.Center)
  .padding({ top: 16, bottom: 16 })
}

日历头部包含左右箭头和当前月份显示,点击箭头可以切换月份。$r语法用于引用资源文件中的图片,这是鸿蒙推荐的资源引用方式。justifyContent设置为Center使内容居中显示。

星期标题行的构建:

@Builder
WeekdayHeader() {
  Row() {
    ForEach(['日', '一', '二', '三', '四', '五', '六'], (day: string) => {
      Text(day)
        .width('14.28%')
        .textAlign(TextAlign.Center)
        .fontSize(14)
        .fontColor('#999999')
    })
  }
  .width('100%')
}

星期标题行使用ForEach循环渲染七个星期文字,每个占据14.28%的宽度(100%/7)。这种百分比布局方式可以自适应不同屏幕宽度,保证日历在各种设备上都能正确显示。

日期网格的构建:

@Builder
DateGrid() {
  Grid() {
    ForEach(this.generateMonthDates(), (date: Date, index: number) => {
      GridItem() {
        this.DateCell(date)
      }
    })
  }
  .columnsTemplate('1fr 1fr 1fr 1fr 1fr 1fr 1fr')
  .rowsGap(8)
  .columnsGap(0)
}

鸿蒙的Grid组件用于构建网格布局,columnsTemplate定义7列等宽布局,1fr表示等分剩余空间。rowsGap设置行间距,使日期之间有适当的视觉间隔。

日期单元格的构建:

@Builder
DateCell(date: Date) {
  Column() {
    Text(`${date.getDate()}`)
      .fontSize(16)
      .fontColor(this.isSelected(date) ? Color.White : 
                 this.isToday(date) ? '#1890FF' : '#333333')
    
    if (this.hasEvents(date)) {
      Circle()
        .width(6)
        .height(6)
        .fill(this.isSelected(date) ? Color.White : '#1890FF')
        .margin({ top: 2 })
    }
  }
  .width('100%')
  .aspectRatio(1)
  .justifyContent(FlexAlign.Center)
  .backgroundColor(this.isSelected(date) ? '#1890FF' : Color.Transparent)
  .borderRadius(20)
  .onClick(() => {
    this.selectedDate = date
    this.onDateSelect(date)
  })
}

日期单元格使用Column垂直排列日期数字和日程指示点。aspectRatio(1)确保单元格为正方形,borderRadius(20)在选中时创建圆形背景效果。条件渲染根据是否有日程决定是否显示指示点。

选中日期的日程列表:

@Builder
EventList() {
  if (this.selectedDate !== null) {
    List({ space: 8 }) {
      ForEach(this.getEventsForDate(this.selectedDate), (event: ScheduleEvent) => {
        ListItem() {
          Row() {
            Divider()
              .vertical(true)
              .height(40)
              .strokeWidth(4)
              .color(event.color)
            
            Column() {
              Text(event.title)
                .fontSize(14)
                .fontWeight(FontWeight.Medium)
              Text(this.formatEventTime(event))
                .fontSize(12)
                .fontColor('#666666')
            }
            .alignItems(HorizontalAlign.Start)
            .margin({ left: 12 })
          }
          .padding(12)
          .backgroundColor('#F5F5F5')
          .borderRadius(8)
        }
      })
    }
    .width('100%')
    .margin({ top: 16 })
  }
}

日程列表展示选中日期的所有日程,每个日程项左侧有一个彩色竖条用于区分类型。Divider组件设置vertical(true)变为垂直分割线,strokeWidth控制宽度,color使用日程的颜色属性。这种设计使日程列表既美观又信息丰富。

总结

本文详细介绍了Flutter和OpenHarmony平台上日程日历组件的开发方法。日历组件是OA系统中复杂度较高的组件之一,需要处理日期计算、视图切换、事件渲染等多个方面。两个平台都提供了灵活的布局组件来实现日历网格,开发者需要根据业务需求选择合适的视图模式和交互方式。一个优秀的日历组件可以显著提升用户的日程管理效率。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐