Flutter水平日历视图插件horizontal_calendar_view_widget的使用

Flutter水平日历视图插件horizontal_calendar_view_widget的使用

horizontal_calendar_view_widget 是一个功能强大的 Flutter 插件,用于创建水平方向的日历视图。它提供了高度可定制化的选项,可以满足各种需求。

特性

  • 自定义日期范围(起始日期和结束日期)
  • 单选或多选日期(最多可选择的天数)
  • 事件回调
    • onDateSelected
    • onDateUnSelected
    • onDateLongTap
    • onMaxDateSelectionReached
  • 支持自定义滚动控制器
  • 初始选中日期
  • 精细控制禁用日期
  • 国际化支持
  • 月份/日期/星期标签顺序自定义
  • 月份/星期标签的显示/隐藏
  • 自定义文本样式(月份、日期、星期)
  • 自定义选中日期的文本样式
  • 自定义月份格式(如 MMMMM
  • 自定义日期格式(如 ddd
  • 自定义星期格式(如 EEEEE
  • 默认日期单元格装饰
  • 选中日期单元格装饰
  • 禁用日期单元格装饰

水平日历演示

属性

属性名称 属性类型 描述 默认值
height double 小部件的高度 100
firstDate DateTime 日历的起始日期 -
lastDate DateTime 日历的结束日期 -
minSelectedDateCount int 可选的最小日期数量 0
maxSelectedDateCount int 可选的最大日期数量 1
onDateSelected Function(DateTime dateTime) 日期被选中时的回调函数 -
onDateLongTap Function(DateTime dateTime) 长按日期单元格时的回调函数 -
onDateUnSelected Function(DateTime dateTime) 日期被取消选中时的回调函数 -
onMaxDateSelectionReached VoidCallback 当达到最大选择日期数量时的回调函数 -
initialSelectedDates List<DateTime> 初始选中的日期列表 空列表
isDateDisabled bool Function(DateTime dateTime) 检查某个日期是否被禁用的函数 -
labelOrder List<LabelType> 标签顺序 [LabelType.month, LabelType.date, LabelType.weekday]
scrollController ScrollController 水平列表的滚动控制器 -
monthTextStyle TextStyle 月份标签的文本样式 titleTheme
selectedMonthTextStyle TextStyle 选中月份标签的文本样式 monthTextStyle
monthFormat String 月份格式 MMM
dateTextStyle TextStyle 日期标签的文本样式 subTitleTheme
selectedDateTextStyle TextStyle 选中日期标签的文本样式 dateTextStyle
dateFormat String 日期格式 dd
weekDayTextStyle TextStyle 星期标签的文本样式 subTitleTheme
selectedWeekDayTextStyle TextStyle 选中星期标签的文本样式 dateTextStyle
weekDayFormat String 星期格式 EEE
defaultDecoration Decoration 默认日期单元格装饰 -
selectedDecoration Decoration 选中日期单元格装饰 -
disabledDecoration Decoration 禁用日期单元格装饰 -
spacingBetweenDates double 两个日期单元格之间的间距 8.0
padding EdgeInsetsGeometry 日期单元格的内边距 EdgeInsets.all(8.0)

状态管理

initialSelectedDates 只会在小部件首次构建时生效。之后,horizontal_calendar 会自行管理日期的选择和取消选择。

如果需要在应用中获得对状态的初始控制,可以传递一个 UniqueKey

HorizontalCalendar(
  key: UniqueKey(),
);

示例代码

以下是一个完整的示例代码,展示了如何使用 horizontal_calendar_view_widget

import 'package:flutter/material.dart';
import 'package:horizontal_calendar_view_widget/date_helper.dart';
import 'package:horizontal_calendar_view_widget/horizontal_calendar.dart';
import 'package:intl/intl.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Horizontal Calendar Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(title: Text('Horizontal Calendar Demo')),
        body: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 16),
          child: DemoWidget(),
        ),
      ),
    );
  }
}

const labelMonth = 'Month';
const labelDate = 'Date';
const labelWeekDay = 'Week Day';

class DemoWidget extends StatefulWidget {
  const DemoWidget({Key key}) : super(key: key);

  [@override](/user/override)
  _DemoWidgetState createState() => _DemoWidgetState();
}

class _DemoWidgetState extends State<DemoWidget> {
  DateTime firstDate;
  DateTime lastDate;
  String dateFormat = 'dd';
  String monthFormat = 'MMM';
  String weekDayFormat = 'EEE';
  List<String> order = [labelMonth, labelDate, labelWeekDay];
  bool forceRender = false;

  Color defaultDecorationColor = Colors.transparent;
  BoxShape defaultDecorationShape = BoxShape.rectangle;
  bool isCircularRadiusDefault = true;

  Color selectedDecorationColor = Colors.green;
  BoxShape selectedDecorationShape = BoxShape.rectangle;
  bool isCircularRadiusSelected = true;

  Color disabledDecorationColor = Colors.grey;
  BoxShape disabledDecorationShape = BoxShape.rectangle;
  bool isCircularRadiusDisabled = true;

  int minSelectedDateCount = 1;
  int maxSelectedDateCount = 1;
  RangeValues selectedDateCount;

  List<DateTime> initialSelectedDates;

  [@override](/user/override)
  void initState() {
    super.initState();
    const int days = 30;
    firstDate = toDateMonthYear(DateTime.now());
    lastDate = toDateMonthYear(firstDate.add(Duration(days: days - 1)));
    selectedDateCount = RangeValues(
      minSelectedDateCount.toDouble(),
      maxSelectedDateCount.toDouble(),
    );
    initialSelectedDates = feedInitialSelectedDates(minSelectedDateCount, days);
  }

  List<DateTime> feedInitialSelectedDates(int target, int calendarDays) {
    List<DateTime> selectedDates = [];

    for (int i = 0; i < calendarDays; i++) {
      if (selectedDates.length == target) {
        break;
      }
      DateTime date = DateTime.now().add(Duration(days: 10));
      if (date.weekday != DateTime.sunday) {
        selectedDates.add(date);
      }
    }

    return selectedDates;
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: <Widget>[
        SizedBox(height: 16),
        HorizontalCalendar(
          key: forceRender ? UniqueKey() : Key('Calendar'),
          height: 120,
          padding: EdgeInsets.all(22),
          firstDate: firstDate,
          lastDate: lastDate,
          dateFormat: dateFormat,
          weekDayFormat: weekDayFormat,
          monthFormat: monthFormat,
          defaultDecoration: BoxDecoration(
            color: defaultDecorationColor,
            shape: defaultDecorationShape,
            borderRadius: defaultDecorationShape == BoxShape.rectangle && isCircularRadiusDefault
                ? BorderRadius.circular(8)
                : null,
          ),
          selectedDecoration: BoxDecoration(
            color: selectedDecorationColor,
            shape: selectedDecorationShape,
            borderRadius: selectedDecorationShape == BoxShape.rectangle && isCircularRadiusSelected
                ? BorderRadius.circular(8)
                : null,
          ),
          disabledDecoration: BoxDecoration(
            color: disabledDecorationColor,
            shape: disabledDecorationShape,
            borderRadius: disabledDecorationShape == BoxShape.rectangle && isCircularRadiusDisabled
                ? BorderRadius.circular(8)
                : null,
          ),
          isDateDisabled: (date) => date.weekday == DateTime.sunday,
          labelOrder: order.map(toLabelType).toList(),
          minSelectedDateCount: minSelectedDateCount,
          maxSelectedDateCount: maxSelectedDateCount,
          initialSelectedDates: initialSelectedDates,
        ),
        SizedBox(height: 32),
        Expanded(
          child: ListView(
            children: <Widget>[
              Header(headerText: 'Date Ranges'),
              Row(
                children: <Widget>[
                  Expanded(
                    child: PropertyLabel(
                      label: 'First Date',
                      value: Text(DateFormat('dd/MM/yyyy').format(firstDate)),
                      onTap: () async {
                        final date = await datePicker(context, firstDate);
                        if (date == null) {
                          return;
                        }

                        if (lastDate.isBefore(date)) {
                          showMessage('First Date cannot be after Last Date');
                          return;
                        }

                        int min = minSelectedDateCount;
                        if (!isRangeValid(date, lastDate, min)) {
                          showMessage(
                            "Date range is too low to set this configuration",
                          );
                          return;
                        }

                        setState(() {
                          forceRender = true;
                          dateRangeChange(date, lastDate);
                        });
                      },
                    ),
                  ),
                  Expanded(
                    child: PropertyLabel(
                      label: 'Last Date',
                      value: Text(DateFormat('dd/MM/yyyy').format(lastDate)),
                      onTap: () async {
                        final date = await datePicker(context, lastDate);
                        if (date == null) {
                          return;
                        }

                        if (firstDate.isAfter(date)) {
                          showMessage(
                            'Last Date cannot be before First Date',
                          );
                          return;
                        }

                        int min = minSelectedDateCount;
                        if (!isRangeValid(firstDate, date, min)) {
                          showMessage(
                            "Date range is too low to set this configuration",
                          );
                          return;
                        }

                        setState(() {
                          forceRender = true;
                          dateRangeChange(firstDate, date);
                        });
                      },
                    ),
                  ),
                ],
              ),
              Header(headerText: 'Date Selection'),
              PropertyLabel(
                label: 'Min-Max Selectable Dates ($minSelectedDateCount - $maxSelectedDateCount)',
                value: CustomRangeSlider(
                  range: selectedDateCount,
                  min: 0,
                  max: 15,
                  onRangeSet: (newRange) {
                    selectedDateCount = newRange;
                  },
                ),
              ),
              ElevatedButton(
                child: Text('Update'),
                onPressed: () {
                  setState(() {
                    int min = selectedDateCount.start.toInt();
                    if (!isRangeValid(firstDate, lastDate, min)) {
                      showMessage(
                        "Date range is too low to set this configuration",
                      );
                      return;
                    }

                    minSelectedDateCount = selectedDateCount.start.toInt();
                    maxSelectedDateCount = selectedDateCount.end.toInt();
                    initialSelectedDates = feedInitialSelectedDates(
                      minSelectedDateCount,
                      daysCount(firstDate, lastDate),
                    );
                    showMessage("Updated");
                  });
                },
              ),
              Header(headerText: 'Formats'),
              PropertyLabel(
                label: 'Date Format',
                value: DropDownProperty(
                  hint: 'Select Date Format',
                  value: dateFormat,
                  options: ['dd', 'dd/MM'],
                  onChange: (format) {
                    setState(() {
                      forceRender = false;
                      dateFormat = format;
                    });
                  },
                ),
              ),
              PropertyLabel(
                label: 'Month Format',
                value: DropDownProperty(
                  hint: 'Select Month Format',
                  value: monthFormat,
                  options: ['MM', 'MMM'],
                  onChange: (format) {
                    setState(() {
                      forceRender = false;
                      monthFormat = format;
                    });
                  },
                ),
              ),
              PropertyLabel(
                label: 'WeekDay Format',
                value: DropDownProperty(
                  hint: 'Select Weekday Format',
                  value: weekDayFormat,
                  options: ['EEE', 'EEEE'],
                  onChange: (format) {
                    setState(() {
                      forceRender = false;
                      weekDayFormat = format;
                    });
                  },
                ),
              ),
              Header(headerText: 'Labels'),
              PropertyLabel(
                label: 'Label Orders (Drag & Drop to reorder)',
                value: Align(
                  alignment: Alignment.centerLeft,
                  child: Row(
                    children: <Widget>[
                      SizedBox(
                        height: 200,
                        width: 150,
                        child: ReorderableListView(
                          children: order
                              .map(
                                (listItem) => Align(
                                  key: Key(listItem),
                                  heightFactor: 1,
                                  alignment: Alignment.centerLeft,
                                  child: Chip(
                                    onDeleted: () => listItem != labelDate
                                        ? setState(() {
                                            forceRender = false;
                                            order.remove(listItem);
                                          })
                                        : null,
                                    deleteIcon: listItem != labelDate
                                        ? Icon(Icons.cancel)
                                        : null,
                                    label: Text(listItem),
                                  ),
                                ),
                              )
                              .toList(),
                          onReorder: (oldIndex, newIndex) {
                            setState(() {
                              forceRender = false;
                              if (newIndex > oldIndex) {
                                newIndex -= 1;
                              }
                              final item = order.removeAt(oldIndex);
                              order.insert(newIndex, item);
                            });
                          },
                        ),
                      ),
                      ElevatedButton(
                        child: Text('Add Labels'),
                        onPressed: () {
                          setState(() {
                            forceRender = false;
                            if (!order.contains(labelMonth)) {
                              order.add(labelMonth);
                            }
                            if (!order.contains(labelWeekDay)) {
                              order.add(labelWeekDay);
                            }
                          });
                        },
                      )
                    ],
                  ),
                ),
              ),
              Header(headerText: 'Default Decoration'),
              DecorationBuilder(
                decorationShape: defaultDecorationShape,
                onSelectShape: (value) {
                  setState(() {
                    forceRender = false;
                    defaultDecorationShape = value;
                  });
                },
                isCircularRadius: isCircularRadiusDefault,
                onCircularRadiusChange: (isSelected) {
                  setState(
                    () {
                      isCircularRadiusDefault = isSelected;
                    },
                  );
                },
                color: defaultDecorationColor,
                onColorChange: (value) {
                  setState(() {
                    forceRender = false;
                    defaultDecorationColor = value;
                  });
                },
              ),
              Header(headerText: 'Selected Decoration'),
              DecorationBuilder(
                decorationShape: selectedDecorationShape,
                onSelectShape: (value) {
                  setState(() {
                    forceRender = false;
                    selectedDecorationShape = value;
                  });
                },
                isCircularRadius: isCircularRadiusSelected,
                onCircularRadiusChange: (isSelected) {
                  setState(
                    () {
                      isCircularRadiusSelected = isSelected;
                    },
                  );
                },
                color: selectedDecorationColor,
                onColorChange: (value) {
                  setState(() {
                    forceRender = false;
                    selectedDecorationColor = value;
                  });
                },
              ),
              Header(headerText: 'Disabled Decoration'),
              DecorationBuilder(
                decorationShape: disabledDecorationShape,
                onSelectShape: (value) {
                  setState(() {
                    forceRender = false;
                    disabledDecorationShape = value;
                  });
                },
                isCircularRadius: isCircularRadiusDisabled,
                onCircularRadiusChange: (isSelected) {
                  setState(
                    () {
                      isCircularRadiusDisabled = isSelected;
                    },
                  );
                },
                color: disabledDecorationColor,
                onColorChange: (value) {
                  setState(() {
                    forceRender = false;
                    disabledDecorationColor = value;
                  });
                },
              )
            ],
          ),
        )
      ],
    );
  }

  void showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(message)),
    );
  }

  bool isRangeValid(DateTime first, DateTime last, int minSelection) {
    int availableDays = availableDaysCount(
      getDateList(first, last),
      [DateTime.sunday],
    );

    return availableDays >= minSelection;
  }

  int availableDaysCount(List<DateTime> dates, List<int> disabledDays) =>
      dates.where((date) => !disabledDays.contains(date.weekday)).length;

  void dateRangeChange(DateTime first, DateTime last) {
    firstDate = first;
    lastDate = last;
    initialSelectedDates = feedInitialSelectedDates(
      minSelectedDateCount,
      daysCount(first, last),
    );
    selectedDateCount = RangeValues(
      minSelectedDateCount.toDouble(),
      maxSelectedDateCount.toDouble(),
    );
  }
}

Future<DateTime> datePicker(
  BuildContext context,
  DateTime initialDate,
) async {
  final selectedDate = await showDatePicker(
    context: context,
    initialDate: initialDate,
    firstDate: DateTime.now().subtract(
      Duration(days: 365),
    ),
    lastDate: DateTime.now().add(
      Duration(days: 365),
    ),
  );
  return toDateMonthYear(selectedDate);
}

LabelType toLabelType(String label) {
  LabelType type;
  switch (label) {
    case labelMonth:
      type = LabelType.month;
      break;
    case labelDate:
      type = LabelType.date;
      break;
    case labelWeekDay:
      type = LabelType.weekday;
      break;
  }
  return type;
}

String fromLabelType(LabelType label) {
  String labelString;
  switch (label) {
    case LabelType.month:
      labelString = labelMonth;
      break;
    case LabelType.date:
      labelString = labelDate;
      break;
    case LabelType.weekday:
      labelString = labelWeekDay;
      break;
  }
  return labelString;
}
1 回复

更多关于Flutter水平日历视图插件horizontal_calendar_view_widget的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


horizontal_calendar_view_widget 是一个用于在 Flutter 应用中显示水平滚动日历的插件。它允许用户以水平滚动的方式查看日期,并且可以自定义日历的外观和行为。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 horizontal_calendar_view_widget 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  horizontal_calendar_view_widget: ^1.0.0  # 请使用最新版本

然后运行 flutter pub get 来安装插件。

基本用法

以下是一个简单的示例,展示如何使用 horizontal_calendar_view_widget 插件来显示一个水平滚动的日历:

import 'package:flutter/material.dart';
import 'package:horizontal_calendar_view_widget/horizontal_calendar_view_widget.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Horizontal Calendar Example'),
        ),
        body: HorizontalCalendarView(
          startDate: DateTime.now().subtract(Duration(days: 30)),
          endDate: DateTime.now().add(Duration(days: 30)),
          selectedDate: DateTime.now(),
          onDateSelected: (DateTime date) {
            print('Selected date: $date');
          },
        ),
      ),
    );
  }
}

参数说明

  • startDate: 日历的起始日期。
  • endDate: 日历的结束日期。
  • selectedDate: 当前选中的日期。
  • onDateSelected: 当用户选择日期时触发的回调函数。

自定义外观

你可以通过传递不同的参数来自定义日历的外观。例如,你可以更改日期的颜色、背景颜色、字体大小等。

HorizontalCalendarView(
  startDate: DateTime.now().subtract(Duration(days: 30)),
  endDate: DateTime.now().add(Duration(days: 30)),
  selectedDate: DateTime.now(),
  onDateSelected: (DateTime date) {
    print('Selected date: $date');
  },
  dateTextStyle: TextStyle(color: Colors.black, fontSize: 16),
  selectedDateTextStyle: TextStyle(color: Colors.white, fontSize: 18),
  selectedDateBackgroundColor: Colors.blue,
  dateBackgroundColor: Colors.grey[200],
  monthTextStyle: TextStyle(color: Colors.black, fontSize: 20),
)

其他功能

  • 禁用日期: 你可以通过 disabledDates 参数来禁用某些日期,使其不可选择。
  • 自定义日期格式: 你可以通过 dateFormat 参数来自定义日期的显示格式。
HorizontalCalendarView(
  startDate: DateTime.now().subtract(Duration(days: 30)),
  endDate: DateTime.now().add(Duration(days: 30)),
  selectedDate: DateTime.now(),
  onDateSelected: (DateTime date) {
    print('Selected date: $date');
  },
  disabledDates: [DateTime.now().add(Duration(days: 1))],
  dateFormat: 'dd/MM',
)
回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!