Flutter水平日历视图插件horizontal_calendar_view_widget的使用
Flutter水平日历视图插件horizontal_calendar_view_widget的使用
horizontal_calendar_view_widget
是一个功能强大的 Flutter 插件,用于创建水平方向的日历视图。它提供了高度可定制化的选项,可以满足各种需求。
特性
- 自定义日期范围(起始日期和结束日期)
- 单选或多选日期(最多可选择的天数)
- 事件回调:
onDateSelected
onDateUnSelected
onDateLongTap
onMaxDateSelectionReached
- 支持自定义滚动控制器
- 初始选中日期
- 精细控制禁用日期
- 国际化支持
- 月份/日期/星期标签顺序自定义
- 月份/星期标签的显示/隐藏
- 自定义文本样式(月份、日期、星期)
- 自定义选中日期的文本样式
- 自定义月份格式(如
MM
、MMM
) - 自定义日期格式(如
dd
、d
) - 自定义星期格式(如
EE
、EEE
) - 默认日期单元格装饰
- 选中日期单元格装饰
- 禁用日期单元格装饰
属性
属性名称 | 属性类型 | 描述 | 默认值 |
---|---|---|---|
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',
)