Flutter日历组件插件calendar_components的使用
Flutter日历组件插件calendar_components的使用
开始使用
首先,在你的pubspec.yaml
文件中添加以下依赖:
dependencies:
calendar_components: ^0.1.0
然后运行flutter pub get
以安装该包。
可用组件
该插件提供了几个简单的组件,帮助你构建自己的日历。这些组件允许你自己处理设计和状态管理。下面是一些主要组件的介绍及示例代码。
日历头部组件 CalendarComponentHeader
用于展示日历顶部的头部信息。通常包含一周七天的名称。
CalendarComponentHeader(
itemBuilder: (day) => Text(day.name),
)
基础日历网格 CalendarComponentDayGrid
用于展示基本的日历网格。根据是否需要处理溢出日期,可以选择不同的构造函数。
处理溢出日期
final currentMonth = DateTime.now();
CalendarComponentDayGrid.overflow(
currentMonth: currentMonth,
itemBuilder: (context, date, index) {
return Text('${date.day}');
},
)
不处理溢出日期
CalendarComponentDayGrid.noOverflow(
currentMonth: currentMonth,
startDate: currentMonth.copyWith(day: 1),
endDate: currentMonth.lastDayOfCurrentMonth(),
itemBuilder: (context, date, index) {
return Text('${date.day}');
},
);
单选或多选日历网格
允许用户选择一个或多个日期。这些是无状态小部件,你需要自己管理选择的状态。
单选日历网格
final currentMonth = DateTime.now();
CalendarComponentSingleSelectableDayGrid.overflow(
selectedDate: ...,
currentMonth: currentMonth,
itemBuilder: (context, date, isSelected, index) {
return GestureDetector(
onTap: () => setState(() {
// 更新选择状态
}),
child: Text('${date.day}'),
);
},
)
多选日历网格
CalendarComponentMultipleSelectableDayGrid.noOverflow(
selectedDates: [...],
currentMonth: currentMonth,
startDate: currentMonth.copyWith(day: 1),
endDate: currentMonth.lastDayOfCurrentMonth(),
itemBuilder: (context, date, isSelected, index) {
return GestureDetector(
onTap: () => setState(() {
// 更新选择状态
}),
child: Text('${date.day}'),
);
},
);
区间选择日历网格 CalendarComponentRangedSelectableDayGrid
允许用户选择一个日期区间。这也是一个无状态小部件,你需要自己管理选择的日期范围。
CalendarComponentRangedSelectableDayGrid.overflow(
selectedStartDate: ...,
selectedEndDate: ...,
currentMonth: DateTime.now(),
itemBuilder: (context, date, selectedState, index) {
return GestureDetector(
onTap: () => setState(() {
// 更新选择状态
}),
child: Text('${date.day}'),
);
},
)
完整示例代码
以下是一个完整的示例代码,展示了如何使用这些组件来创建一个日历应用。
import 'package:calendar_components/calendar_components.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
void main() {
runApp(const MyApp());
}
extension on GregorianCalendarDay {
String get shorterName {
switch (this) {
case GregorianCalendarDay.sunday:
return 'Sun';
case GregorianCalendarDay.monday:
return 'Mon';
case GregorianCalendarDay.tuesday:
return 'Tue';
case GregorianCalendarDay.wednesday:
return 'Wed';
case GregorianCalendarDay.thursday:
return 'Thu';
case GregorianCalendarDay.friday:
return 'Fri';
case GregorianCalendarDay.saturday:
return 'Sat';
}
}
}
extension on DateTime {
bool isSameMonthAs(DateTime other) {
return year == other.year && month == other.month;
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
[@override](/user/override)
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
Locale('en', 'GB'),
],
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
[@override](/user/override)
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
DateTime? _selectedStartDate;
DateTime? _selectedEndDate;
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: LayoutBuilder(builder: (context, constraints) {
const horizontalPadding = 18.0;
final itemExtent = (constraints.maxWidth - 2 * horizontalPadding) /
DateTime.daysPerWeek;
final startDate = DateTime.now();
return Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: horizontalPadding),
child: CalendarComponentHeader(
itemBuilder: (context, day) => Container(
padding: const EdgeInsets.symmetric(vertical: 10),
width: itemExtent,
child: Text(
day.shorterName,
textAlign: TextAlign.center,
style: const TextStyle(color: Color(0xff717171)),
),
),
),
),
Expanded(
child: ListView.separated(
itemBuilder: (context, index) {
final month = DateTime(startDate.year, startDate.month + index);
return PartialCalendarDayGrid(
itemExtent: itemExtent,
horizontalPadding: horizontalPadding,
currentMonth: month,
startDate: month.copyWith(
day: month.isSameMonthAs(startDate) ? 18 : 1),
endDate: month.lastDateOfCurrentMonth(),
selectedStartDate: _selectedStartDate,
selectedEndDate: _selectedEndDate,
isDateAvailable: (date) => ![
for (var i = 18; i <= 24; i++)
startDate.copyWith(day: i).toMidnight()
].contains(date),
onDateTapped: _onDateTapped,
);
},
separatorBuilder: (_, __) => const SizedBox(height: 16),
itemCount: 12,
),
),
],
);
}),
),
);
}
void _onDateTapped(DateTime date) {
setState(() {
final selectedStartDate = _selectedStartDate;
final selectedEndDate = _selectedEndDate;
if (selectedStartDate == null) {
_selectedStartDate = date;
return;
}
if (selectedEndDate == null) {
// 允许取消选择同一日期
if (date.isAtSameMomentAs(selectedStartDate)) {
_selectedStartDate = null;
_selectedEndDate = null;
return;
} else {
_selectedEndDate = date;
return;
}
}
if (date.isAtSameMomentAs(selectedStartDate)) {
_selectedStartDate = date;
_selectedEndDate = null;
return;
}
if (date.isAtSameMomentAs(selectedEndDate)) {
_selectedStartDate = date;
_selectedEndDate = null;
return;
}
_selectedEndDate = null;
_selectedStartDate = date;
return;
});
}
}
class PartialCalendarDayGrid extends StatelessWidget {
const PartialCalendarDayGrid({
super.key,
double? horizontalPadding,
required this.itemExtent,
required this.currentMonth,
required this.startDate,
required this.selectedStartDate,
required this.selectedEndDate,
required this.endDate,
this.isDateAvailable,
this.onDateTapped,
}) : horizontalPadding = horizontalPadding ?? 0;
final double horizontalPadding;
final double itemExtent;
final DateTime currentMonth;
final DateTime startDate;
final DateTime endDate;
final DateTime? selectedStartDate;
final DateTime? selectedEndDate;
final bool Function(DateTime)? isDateAvailable;
final ValueChanged<DateTime>? onDateTapped;
static final months = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
[@override](/user/override)
Widget build(BuildContext context) {
final month = months[currentMonth.month - 1];
final year = currentMonth.year;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: 16, bottom: 12, left: horizontalPadding),
child: Text(
'$month $year',
style: const TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding),
child: CalendarComponentRangedSelectableDayGrid.noOverflow(
currentMonth: currentMonth,
startDate: startDate,
endDate: endDate,
selectedStartDate: selectedStartDate,
selectedEndDate: selectedEndDate,
itemBuilder: _itemBuilder,
),
),
],
);
}
Widget _itemBuilder(
BuildContext context,
DateTime date,
SelectedDateRangeState? selectedState,
int index,
) {
final isDateAvailable = this.isDateAvailable?.call(date) ?? true;
final onDateTapped = isDateAvailable ? this.onDateTapped : null;
return PartialCalendarDayGridItem(
date,
currentMonth: currentMonth,
startDate: startDate,
endDate: endDate,
itemExtent: itemExtent,
itemIndex: index,
selectedState: selectedState,
onDateTapped: onDateTapped,
);
}
}
class PartialCalendarDayGridItem extends StatelessWidget {
const PartialCalendarDayGridItem(
this.date, {
super.key,
required this.currentMonth,
required this.startDate,
required this.endDate,
required this.itemExtent,
required this.itemIndex,
required this.selectedState,
this.onDateTapped,
});
final DateTime date;
final DateTime currentMonth;
final DateTime startDate;
final DateTime endDate;
final double itemExtent;
final int itemIndex;
final SelectedDateRangeState? selectedState;
final ValueChanged<DateTime>? onDateTapped;
[@override](/user/override)
Widget build(BuildContext context) {
if (!date.isSameMonthAs(currentMonth) ||
date.isBefore(startDate) ||
date.isAfter(endDate)) {
return SizedBox(width: itemExtent, height: itemExtent);
}
final selectedDecoration = BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.circular(itemExtent / 2),
);
const inBetweenColor = Color(0xffe8e8e8);
const disabledColor = Color(0xffd8d8d8);
final inBetweenDecoration = BoxDecoration(
color: inBetweenColor,
gradient: () {
final isLeftMostDate = itemIndex % DateTime.daysPerWeek == 0;
final isRightMostDate = (itemIndex + 1) % DateTime.daysPerWeek == 0;
final isDateFirstOfTheMonth =
date.isAtSameMomentAs(currentMonth.copyWith(day: 1));
final isDateLastOfTheMonth =
date.isAtSameMomentAs(currentMonth.lastDateOfCurrentMonth());
if (!isLeftMostDate &&
!isRightMostDate &&
!isDateFirstOfTheMonth &&
!isDateLastOfTheMonth) {
return null;
}
return LinearGradient(
colors: [
inBetweenColor,
Theme.of(context).scaffoldBackgroundColor,
],
stops: const [0.65, 1],
begin: isLeftMostDate || isDateFirstOfTheMonth
? Alignment.centerRight
: Alignment.centerLeft,
end: isRightMostDate || isDateLastOfTheMonth
? Alignment.centerRight
: Alignment.centerLeft,
);
}(),
);
const margin = EdgeInsets.all(4);
final inBetweenMargin = margin.copyWith(left: 0, right: 0);
final connectedEndMargin = margin.copyWith(
left: selectedState == SelectedDateRangeState.startDateConnected
? itemExtent / 2
: 0,
right: selectedState == SelectedDateRangeState.endDateConnected
? itemExtent / 2
: 0,
);
final isValidDateRange = selectedState?.isValidDateRange ?? false;
final isSelected = selectedState?.isSelected ?? false;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => onDateTapped?.call(date),
child: SizedBox(
width: itemExtent,
height: itemExtent,
child: Stack(
children: [
if (isValidDateRange)
Container(
decoration: inBetweenDecoration,
margin: selectedState != null
? connectedEndMargin
: inBetweenMargin,
),
Container(
margin: margin,
decoration: isSelected ? selectedDecoration : null,
child: Center(
child: Text(
date.day.toString(),
style: TextStyle(
color: onDateTapped == null
? disabledColor
: isSelected
? Colors.white
: Colors.black,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
),
);
}
}
更多关于Flutter日历组件插件calendar_components的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter日历组件插件calendar_components的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在 Flutter 中,calendar_components
是一个用于构建日历相关 UI 的插件。它提供了丰富的组件和功能,可以帮助开发者快速实现日历视图、事件管理、日程安排等功能。以下是如何使用 calendar_components
插件的基本步骤。
1. 添加依赖
首先,你需要在 pubspec.yaml
文件中添加 calendar_components
插件的依赖:
dependencies:
flutter:
sdk: flutter
calendar_components: ^0.0.1 # 请使用最新版本
然后,运行 flutter pub get
来获取依赖。
2. 导入包
在你的 Dart 文件中,导入 calendar_components
包:
import 'package:calendar_components/calendar_components.dart';
3. 使用日历组件
calendar_components
提供了多种日历相关的组件,例如 CalendarView
、EventView
等。以下是一个简单的示例,展示如何使用 CalendarView
:
import 'package:flutter/material.dart';
import 'package:calendar_components/calendar_components.dart';
class CalendarScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar Example'),
),
body: CalendarView(
initialDate: DateTime.now(),
onDateSelected: (DateTime date) {
print("Selected date: $date");
},
events: [
Event(
title: 'Meeting',
date: DateTime.now(),
description: 'Team meeting at 10 AM',
),
Event(
title: 'Lunch',
date: DateTime.now().add(Duration(days: 1)),
description: 'Lunch with John',
),
],
),
);
}
}
4. 自定义日历样式
你可以通过 CalendarView
的各种属性来自定义日历的外观和行为。例如,可以设置日历的初始日期、事件列表、日期选择回调等。
CalendarView(
initialDate: DateTime.now(),
onDateSelected: (DateTime date) {
print("Selected date: $date");
},
events: [
Event(
title: 'Meeting',
date: DateTime.now(),
description: 'Team meeting at 10 AM',
),
Event(
title: 'Lunch',
date: DateTime.now().add(Duration(days: 1)),
description: 'Lunch with John',
),
],
calendarStyle: CalendarStyle(
selectedColor: Colors.blue,
todayColor: Colors.green,
eventMarkerColor: Colors.red,
),
)
5. 处理事件
你可以通过 Event
类来创建和管理事件。每个事件可以包含标题、日期、描述等信息。你可以在日历中显示这些事件,并在用户选择某个日期时触发相应的回调。
Event(
title: 'Meeting',
date: DateTime.now(),
description: 'Team meeting at 10 AM',
)
6. 运行项目
完成上述步骤后,你可以运行你的 Flutter 项目,查看日历组件的效果。
flutter run