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]),
    );
  }
}

代码解释

  1. 导入库

    import 'dart:io';
    import 'dart:math';
    
    import 'package:flutter/material.dart';
    import 'package:flutter_calendar_view/flutter_calendar_view.dart';
    
  2. 定义主应用类

    void main() {
      runApp(const MyApp());
    }
    
  3. 定义数据模型

    class ExampleObject {
      final Color color;
      DateTime start;
      DateTime end;
    
      ExampleObject(this.color, this.start, this.end);
    }
    
  4. 定义状态管理类

    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('清除'),
                          ),
                        ],
                      ),
                    ),
                  )
                ],
              ),
            ),
          ),
        );
      }
    
  5. 生成随机项目

    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,
      );
    }
    
  6. 更新项目范围

    void _updateItemRange(int index, DateTimeRange updated) {
      setState(() {
        children[index].start = updated.start;
        children[index].end = updated.end;
      });
    }
    
  7. 定义日历项组件

    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),
          ),
        ),
      );
    }
    
  8. 定义子部件

    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,
              ),
            ),
          ),
        );
      }
    }
    
  9. 辅助扩展方法

    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

1 回复

更多关于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 提供了多种视图模式,包括 MonthViewWeekViewDayView。你可以根据需要切换视图:

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),
              ),
            );
          },
        ),
      ),
    );
  }
}
回到顶部