Flutter日历视图插件flutter_calendar_view的使用
Flutter日历视图插件flutter_calendar_view的使用
flutter_calendar_view
是一个用于渲染动态日历视图的包。它可以帮助开发者轻松地在 Flutter 应用程序中实现日历功能。
完整示例Demo
以下是一个完整的示例,展示了如何使用 flutter_calendar_view
插件来创建一个日历视图。
import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_calendar_view/flutter_calendar_view.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class ExampleObject {
final Color color;
DateTime start;
DateTime end;
ExampleObject(this.color, this.start, this.end);
}
class _MyAppState extends State<MyApp> {
final List<ExampleObject> children = [];
late final ScrollController controller = ScrollController(initialScrollOffset: 8 * 40.0);
bool isDragging = false;
@override
Widget build(BuildContext context) {
// 在桌面端我们有鼠标进行滚动交互,在移动设备上仅在拖动时禁用滚动。
final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux;
return MaterialApp(
title: 'flutter_calendar_view',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: const Text('flutter_calendar_view 示例'),
),
body: Container(
color: Colors.white,
child: Stack(
children: [
Positioned.fill(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 20),
physics: isDragging && !isDesktop
? const NeverScrollableScrollPhysics()
: const AlwaysScrollableScrollPhysics(),
child: DayViewWidget(
height: 24 * 40,
date: DateTime.now(),
dragStep: const Duration(minutes: 5),
onDraggingStateChange: (isDragging) {
setState(() => this.isDragging = isDragging);
},
onNewItemBuilder: () {
return Container(
color: Colors.green.withOpacity(.5),
child: const Center(
child: Text('新项目'),
),
);
},
onNewEvent: (range) {
setState(() {
children.add(ExampleObject(Colors.orange, range.start, range.end));
});
},
children: children
.asMap()
.entries
.map(
(m) => item(m.key, m.value, (updated) => _updateItemRange(m.key, updated)),
)
.toList(),
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
color: Colors.black.withOpacity(.5),
padding: const EdgeInsets.all(16),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
setState(() => children.add(randomItem()));
},
style: const ButtonStyle(
padding: MaterialStatePropertyAll(EdgeInsets.all(20)),
),
child: const Text('添加随机项目'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => setState(children.clear),
style: const ButtonStyle(
padding: MaterialStatePropertyAll(EdgeInsets.all(20)),
),
child: const Text('清除'),
),
],
),
),
)
],
),
),
),
);
}
ExampleObject randomItem() {
final now = DateTime.now().midnight;
final start = now.add(Duration(hours: Random().nextInt(20), minutes: Random().nextInt(11) * 5));
final end = start.add(Duration(hours: 1 + Random().nextInt(2)));
return ExampleObject(
Colors.primaries[start.hour % Colors.primaries.length],
start,
end,
);
}
void _updateItemRange(int index, DateTimeRange updated) {
setState(() {
children[index].start = updated.start;
children[index].end = updated.end;
});
}
}
DayItemWidget item(
int i,
ExampleObject item,
ValueChanged<DateTimeRange>? onItemUpdated,
) {
final backgroundColor = item.color;
final start = item.start;
final end = item.end;
const handleSize = 5.0;
final outerPaint = Paint()
..color = backgroundColor
..style = PaintingStyle.fill;
final innerPaint = Paint()
..color = Colors.white
..style = PaintingStyle.fill;
void topHandlePainter(Canvas canvas, Size size) {
canvas.drawCircle(Offset((size.width * 0.8), 1), handleSize, outerPaint);
canvas.drawCircle(Offset((size.width * 0.8), 1), handleSize - 2, innerPaint);
}
void bottomHandlePainter(Canvas canvas, Size size) {
canvas.drawCircle(Offset((size.width * 0.2), size.height - 1), 5, outerPaint);
canvas.drawCircle(Offset((size.width * 0.2), size.height - 1), handleSize - 2, innerPaint);
}
return DayItemWidget(
item: item,
start: start,
end: end,
toggleDraggableAction: ToggleDraggableAction.onLongPress,
drawTopDragHandle: topHandlePainter,
drawBottomDragHandle: bottomHandlePainter,
onItemDragEnd: onItemUpdated,
child: _Item(
color: backgroundColor,
child: Text(
'项目 $i',
style: const TextStyle(color: Colors.black),
),
),
);
}
class _Item extends StatelessWidget {
const _Item({
required this.child,
required this.color,
});
final Widget child;
final Color color;
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(1),
child: Material(
borderRadius: BorderRadius.circular(4),
color: color,
child: InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(4),
splashColor: Colors.black.withOpacity(0.3),
highlightColor: Colors.black.withOpacity(0.3),
hoverColor: Colors.black.withOpacity(0.1),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: child,
),
),
),
);
}
}
// * 辅助函数
extension DateTimeEx on DateTime {
DateTime get midnight => subtract(Duration(
hours: hour,
minutes: minute,
seconds: second,
milliseconds: millisecond,
microseconds: microsecond,
));
}
extension StringEx on String {
DateTime toDateTime() {
final now = DateTime.now();
final time = split(':');
return DateTime(
now.year,
now.month,
now.day,
int.parse(time[0]),
int.parse(time[1]),
);
}
}
代码解释
-
导入库:
import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_calendar_view/flutter_calendar_view.dart';
-
定义主应用类:
void main() { runApp(const MyApp()); }
-
定义数据模型:
class ExampleObject { final Color color; DateTime start; DateTime end; ExampleObject(this.color, this.start, this.end); }
-
定义状态管理类:
class _MyAppState extends State<MyApp> { final List<ExampleObject> children = []; late final ScrollController controller = ScrollController(initialScrollOffset: 8 * 40.0); bool isDragging = false; @override Widget build(BuildContext context) { // 判断是否为桌面端 final isDesktop = Platform.isMacOS || Platform.isWindows || Platform.isLinux; return MaterialApp( title: 'flutter_calendar_view', theme: ThemeData( primarySwatch: Colors.blue, ), home: Scaffold( appBar: AppBar( title: const Text('flutter_calendar_view 示例'), ), body: Container( color: Colors.white, child: Stack( children: [ Positioned.fill( child: SingleChildScrollView( padding: const EdgeInsets.symmetric(vertical: 20), physics: isDragging && !isDesktop ? const NeverScrollableScrollPhysics() : const AlwaysScrollableScrollPhysics(), child: DayViewWidget( height: 24 * 40, date: DateTime.now(), dragStep: const Duration(minutes: 5), onDraggingStateChange: (isDragging) { setState(() => this.isDragging = isDragging); }, onNewItemBuilder: () { return Container( color: Colors.green.withOpacity(.5), child: const Center( child: Text('新项目'), ), ); }, onNewEvent: (range) { setState(() { children.add(ExampleObject(Colors.orange, range.start, range.end)); }); }, children: children .asMap() .entries .map( (m) => item(m.key, m.value, (updated) => _updateItemRange(m.key, updated)), ) .toList(), ), ), ), Align( alignment: Alignment.bottomCenter, child: Container( width: double.infinity, color: Colors.black.withOpacity(.5), padding: const EdgeInsets.all(16), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton( onPressed: () { setState(() => children.add(randomItem())); }, style: const ButtonStyle( padding: MaterialStatePropertyAll(EdgeInsets.all(20)), ), child: const Text('添加随机项目'), ), const SizedBox(width: 8), ElevatedButton( onPressed: () => setState(children.clear), style: const ButtonStyle( padding: MaterialStatePropertyAll(EdgeInsets.all(20)), ), child: const Text('清除'), ), ], ), ), ) ], ), ), ), ); }
-
生成随机项目:
ExampleObject randomItem() { final now = DateTime.now().midnight; final start = now.add(Duration(hours: Random().nextInt(20), minutes: Random().nextInt(11) * 5)); final end = start.add(Duration(hours: 1 + Random().nextInt(2))); return ExampleObject( Colors.primaries[start.hour % Colors.primaries.length], start, end, ); }
-
更新项目范围:
void _updateItemRange(int index, DateTimeRange updated) { setState(() { children[index].start = updated.start; children[index].end = updated.end; }); }
-
定义日历项组件:
DayItemWidget item( int i, ExampleObject item, ValueChanged<DateTimeRange>? onItemUpdated, ) { final backgroundColor = item.color; final start = item.start; final end = item.end; const handleSize = 5.0; final outerPaint = Paint() ..color = backgroundColor ..style = PaintingStyle.fill; final innerPaint = Paint() ..color = Colors.white ..style = PaintingStyle.fill; void topHandlePainter(Canvas canvas, Size size) { canvas.drawCircle(Offset((size.width * 0.8), 1), handleSize, outerPaint); canvas.drawCircle(Offset((size.width * 0.8), 1), handleSize - 2, innerPaint); } void bottomHandlePainter(Canvas canvas, Size size) { canvas.drawCircle(Offset((size.width * 0.2), size.height - 1), 5, outerPaint); canvas.drawCircle(Offset((size.width * 0.2), size.height - 1), handleSize - 2, innerPaint); } return DayItemWidget( item: item, start: start, end: end, toggleDraggableAction: ToggleDraggableAction.onLongPress, drawTopDragHandle: topHandlePainter, drawBottomDragHandle: bottomHandlePainter, onItemDragEnd: onItemUpdated, child: _Item( color: backgroundColor, child: Text( '项目 $i', style: const TextStyle(color: Colors.black), ), ), ); }
-
定义子部件:
class _Item extends StatelessWidget { const _Item({ required this.child, required this.color, }); final Widget child; final Color color; @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.all(1), child: Material( borderRadius: BorderRadius.circular(4), color: color, child: InkWell( onTap: () {}, borderRadius: BorderRadius.circular(4), splashColor: Colors.black.withOpacity(0.3), highlightColor: Colors.black.withOpacity(0.3), hoverColor: Colors.black.withOpacity(0.1), child: Padding( padding: const EdgeInsets.all(4.0), child: child, ), ), ), ); } }
-
辅助扩展方法:
extension DateTimeEx on DateTime { DateTime get midnight => subtract(Duration( hours: hour, minutes: minute, seconds: second, milliseconds: millisecond, microseconds: microsecond, )); } extension StringEx on String { DateTime toDateTime() { final now = DateTime.now(); final time = split(':'); return DateTime( now.year, now.month, now.day, int.parse(time[0]), int.parse(time[1]), ); } }
更多关于Flutter日历视图插件flutter_calendar_view的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter日历视图插件flutter_calendar_view的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
flutter_calendar_view
是一个用于在 Flutter 应用中显示日历视图的插件。它提供了多种日历视图模式,包括月视图、周视图和日视图,并且支持自定义事件和样式。
以下是使用 flutter_calendar_view
的基本步骤和示例代码:
1. 添加依赖
首先,在你的 pubspec.yaml
文件中添加 flutter_calendar_view
依赖:
dependencies:
flutter:
sdk: flutter
flutter_calendar_view: ^1.0.0 # 请使用最新版本
然后运行 flutter pub get
来安装依赖。
2. 导入包
在你的 Dart 文件中导入 flutter_calendar_view
包:
import 'package:flutter_calendar_view/flutter_calendar_view.dart';
3. 基本使用
以下是一个简单的示例,展示如何使用 flutter_calendar_view
来显示一个默认的月视图:
import 'package:flutter/material.dart';
import 'package:flutter_calendar_view/flutter_calendar_view.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Calendar View Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CalendarExample(),
);
}
}
class CalendarExample extends StatelessWidget {
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar View Example'),
),
body: CalendarControllerProvider(
controller: EventController(),
child: MonthView(),
),
);
}
}
4. 自定义事件
你可以通过 EventController
来添加自定义事件。以下是一个添加事件的示例:
class CalendarExample extends StatelessWidget {
final EventController controller = EventController();
CalendarExample() {
// 添加一个事件
controller.add(
CalendarEvent(
title: 'Meeting with Flutter Team',
description: 'Discuss the new features',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 2)),
),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar View Example'),
),
body: CalendarControllerProvider(
controller: controller,
child: MonthView(),
),
);
}
}
5. 切换视图模式
flutter_calendar_view
提供了多种视图模式,包括 MonthView
、WeekView
和 DayView
。你可以根据需要切换视图:
class CalendarExample extends StatelessWidget {
final EventController controller = EventController();
CalendarExample() {
// 添加一个事件
controller.add(
CalendarEvent(
title: 'Meeting with Flutter Team',
description: 'Discuss the new features',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 2)),
),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar View Example'),
),
body: CalendarControllerProvider(
controller: controller,
child: WeekView(), // 切换到周视图
),
);
}
}
6. 自定义样式
你可以通过 CalendarViewTheme
来自定义日历的样式。以下是一个自定义样式的示例:
class CalendarExample extends StatelessWidget {
final EventController controller = EventController();
CalendarExample() {
// 添加一个事件
controller.add(
CalendarEvent(
title: 'Meeting with Flutter Team',
description: 'Discuss the new features',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 2)),
),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar View Example'),
),
body: CalendarControllerProvider(
controller: controller,
child: CalendarViewTheme(
data: CalendarViewThemeData(
monthViewSettings: MonthViewSettings(
dayPadding: EdgeInsets.all(8),
dayTextStyle: TextStyle(color: Colors.blue),
),
),
child: MonthView(),
),
),
);
}
}
7. 处理事件点击
你可以通过 onEventTap
回调来处理事件的点击:
class CalendarExample extends StatelessWidget {
final EventController controller = EventController();
CalendarExample() {
// 添加一个事件
controller.add(
CalendarEvent(
title: 'Meeting with Flutter Team',
description: 'Discuss the new features',
startTime: DateTime.now(),
endTime: DateTime.now().add(Duration(hours: 2)),
),
);
}
[@override](/user/override)
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Calendar View Example'),
),
body: CalendarControllerProvider(
controller: controller,
child: MonthView(
onEventTap: (CalendarEvent event) {
// 处理事件点击
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(event.title),
content: Text(event.description),
),
);
},
),
),
);
}
}