Flutter日历插件p_calendar的使用

Flutter日历插件p_calendar的使用

p_calendar 是一个事件日历组件。以下是如何在 Flutter 应用程序中使用 p_calendar 的详细示例。

完整示例代码

import 'dart:math';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:p_calendar/p_calendar.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await EventCalendar.disableContextMenu();
  runApp(const App());
}

final BorderSide borderSide = BorderSide(color: Colors.grey.shade300);

class App extends StatefulWidget {
  const App({super.key});

  static AppState of(BuildContext context) {
    return context.findRootAncestorStateOfType<AppState>()!;
  }

  @override
  State<App> createState() => AppState();
}

class AppState extends State<App> {
  ThemeMode _themeMode = ThemeMode.system;

  ThemeMode get themeMode => _themeMode;

  set themeMode(ThemeMode mode) {
    if (mode != _themeMode) {
      setState(() {
        _themeMode = mode;
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Event Calendar',
      themeMode: _themeMode,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
          secondary: Colors.orange,
          secondaryContainer: Colors.purple,
        ),
        dividerColor: Colors.black12,
        useMaterial3: true,
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        colorScheme: const ColorScheme.dark(
          primary: Colors.cyan,
          secondary: Colors.deepPurpleAccent,
        ),
        dividerColor: Colors.white10,
        useMaterial3: true,
      ),
      debugShowCheckedModeBanner: false,
      home: const CalendarPage(),
    );
  }
}

class CalendarPage extends StatefulWidget {
  const CalendarPage({super.key});

  @override
  State<CalendarPage> createState() => _CalendarPageState();
}

class _CalendarPageState extends State<CalendarPage> {
  final OverlayPortalController _overlayController = OverlayPortalController();
  final EventCalendarController _calendarController = EventCalendarController();
  final FocusNode _calendarFocusNode = FocusNode();

  final DateFormat _monthDateFormat = DateFormat.MMMM();

  List<CalendarEvent> _events = [
    CalendarEvent(
      id: 'test',
      start: DateTime.now().startOfDay.add(const Duration(hours: 5)),
      end: DateTime.now().startOfDay.add(const Duration(hours: 5, minutes: 30)),
    )
  ];
  CalendarEvent? _event;
  Rect? _eventRect;

  @override
  void dispose() {
    _calendarController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return OverlayPortal(
      controller: _overlayController,
      overlayChildBuilder: (BuildContext context) {
        return Positioned.fill(
          child: Stack(
            children: [
              Positioned.fill(
                child: GestureDetector(
                  behavior: HitTestBehavior.opaque,
                  onTap: () {
                    _overlayController.hide();
                    _eventRect = null;
                  },
                ),
              ),
              if (_eventRect case Rect rect)
                Positioned(
                  top: rect.topRight.dy + _calendarFocusNode.offset.dy,
                  left: rect.topRight.dx.clamp(
                      0.0, MediaQuery.sizeOf(context).width - rect.width),
                  child: Card(
                    color: Theme.of(context).colorScheme.primary,
                    child: Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.end,
                        children: [
                          IconButton(
                            onPressed: () {
                              final CalendarEvent? event = _event;
                              setState(() {
                                _events = [..._events]..remove(event);
                              });
                              _overlayController.hide();
                              _event = null;
                              _eventRect = null;
                            },
                            icon: const Icon(Icons.delete),
                          ),
                          Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const SizedBox(height: 8.0),
                              Text('Start: ${_event!.start.toIso8601String()}'),
                              const SizedBox(height: 8.0),
                              Text('End: ${_event!.end.toIso8601String()}'),
                              const SizedBox(height: 8.0),
                              Text(
                                'Duration: ${_event!.end.difference(_event!.start)}',
                              ),
                            ],
                          )
                        ],
                      ),
                    ),
                  ),
                ),
            ],
          ),
        );
      },
      child: Scaffold(
        body: Column(
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  ListenableBuilder(
                    listenable: _calendarController,
                    builder: (BuildContext context, Widget? child) {
                      return DecoratedBox(
                        decoration: BoxDecoration(
                          border: Border(
                              bottom: BorderSide(
                            color: Theme.of(context).colorScheme.secondary,
                          )),
                        ),
                        child: RichText(
                          text: TextSpan(
                            style: Theme.of(context).textTheme.titleSmall,
                            children: [
                              TextSpan(
                                text: _monthDateFormat
                                    .format(_calendarController.firstDayOfView),
                              ),
                              if (_calendarController.viewType !=
                                  EventCalendarType.day)
                                if (_calendarController.firstDayOfView.endOfWeek
                                    case DateTime endOfWeek
                                    when endOfWeek.month !=
                                        _calendarController.firstDayOfView
                                            .month) ...[
                                  if (_calendarController.firstDayOfView.year !=
                                      endOfWeek.year)
                                    TextSpan(
                                      text:
                                          ' (${_calendarController.firstDayOfView.year}) ',
                                    ),
                                  TextSpan(
                                    text:
                                        ' / ${_monthDateFormat.format(endOfWeek)}',
                                  ),
                                  TextSpan(
                                    text: ' (${endOfWeek.year}) ',
                                  ),
                                ] else
                                  TextSpan(
                                    text:
                                        ' (${_calendarController.firstDayOfView.year}) ',
                                  )
                            ],
                          ),
                        ),
                      );
                    },
                  ),
                  Flexible(
                    child: ListenableBuilder(
                      listenable: _calendarController,
                      builder: (BuildContext context, Widget? child) {
                        final bool isDayView = _calendarController.viewType ==
                            EventCalendarType.day;
                        return Wrap(
                          alignment: WrapAlignment.center,
                          crossAxisAlignment: WrapCrossAlignment.center,
                          runSpacing: 8.0,
                          children: [
                            Builder(
                              builder: (BuildContext context) {
                                return SizedBox(
                                  width: 100.0,
                                  child: DropdownButtonFormField<ThemeMode>(
                                    decoration: const InputDecoration(
                                      label: Text('Theme'),
                                    ),
                                    onChanged: (ThemeMode? value) {
                                      if (value != null) {
                                        App.of(context).themeMode = value;
                                      }
                                    },
                                    value: App.of(context).themeMode,
                                    items: [
                                      for (final ThemeMode mode
                                          in ThemeMode.values)
                                        DropdownMenuItem(
                                          value: mode,
                                          child: Text(mode.name),
                                        )
                                    ],
                                  ),
                                );
                              },
                            ),
                            const SizedBox(width: 8.0),
                            SizedBox(
                              width: 150.0,
                              child: DropdownButtonFormField<EventCalendarType>(
                                decoration: const InputDecoration(
                                  label: Text('View type'),
                                ),
                                onChanged: (EventCalendarType? value) {
                                  if (value != null) {
                                    _calendarController.viewType = value;
                                  }
                                },
                                value: _calendarController.viewType,
                                items: [
                                  for (final EventCalendarType type
                                      in EventCalendarType.values)
                                    DropdownMenuItem(
                                      value: type,
                                      child: Text(type.name),
                                    )
                                ],
                              ),
                            ),
                            const SizedBox(width: 8.0),
                            Row(
                              mainAxisSize: MainAxisSize.min,
                              crossAxisAlignment: CrossAxisAlignment.center,
                              children: [
                                TextButton(
                                  onPressed: _calendarController.today,
                                  child: const Text('Today'),
                                ),
                                const SizedBox(width: 8.0),
                                IconButton(
                                  tooltip: isDayView
                                      ? 'Previous day'
                                      : 'Previous week',
                                  onPressed:
                                      _calendarController.jumpToPreviousPage,
                                  icon: const Icon(Icons.keyboard_arrow_left),
                                ),
                                const SizedBox(width: 8.0),
                                IconButton(
                                  tooltip: isDayView ? 'Next day' : 'Next week',
                                  onPressed: _calendarController.jumpToNextPage,
                                  icon: const Icon(Icons.keyboard_arrow_right),
                                ),
                              ],
                            ),
                          ],
                        );
                      },
                    ),
                  ),
                ],
              ),
            ),
            const Divider(thickness: 1.0, height: 1.0),
            Flexible(
              child: Focus(
                focusNode: _calendarFocusNode,
                child: EventCalendar(
                  calendarTheme:
                      EventCalendarTheme.fromThemeData(Theme.of(context))
                          .copyWith(
                    unavailableSlotsColor:
                        Theme.of(context).brightness == Brightness.dark
                            ? Colors.grey.shade800
                            : Colors.grey.shade300,
                    dividerColor:
                        Theme.of(context).brightness == Brightness.dark
                            ? Colors.grey.shade700
                            : Colors.grey.shade500,
                  ),
                  controller: _calendarController,
                  minutesPerSlot: 15,
                  events: _events,
                  availableRanges: [
                    (
                      start: DateTime.now().startOfDay,
                      end: DateTime.now()
                          .startOfDay
                          .add(const Duration(hours: 1))
                    ),
                    (
                      start: DateTime.now()
                          .startOfDay
                          .add(const Duration(hours: 5)),
                      end: DateTime.now()
                          .startOfDay
                          .add(const Duration(hours: 8))
                    ),
                    (
                      start: DateTime.now()
                          .startOfDay
                          .add(const Duration(hours: 20)),
                      end: DateTime.now()
                          .startOfDay
                          .add(const Duration(hours: 23))
                    ),
                  ],
                  onEventCreated: (DateRange event) async {
                    setState(() {
                      _events = [
                        ..._events,
                        CalendarEvent(
                          id: event.start.millisecondsSinceEpoch.toString(),
                          start: event.start,
                          end: event.end,
                          color: Color(0xff000000 |
                              (Random().nextDouble() * 0xffffff).toInt()),
                        )
                      ];
                    });
                  },
                  canAddEvent: (DateRange event) {
                    if (event.start.hour case 1 || 13) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(
                          content: const Text(
                            'Events at 1 AM/PM are prohibited',
                          ),
                          backgroundColor:
                              Theme.of(context).colorScheme.errorContainer,
                        ),
                      );
                      return SynchronousFuture<bool>(false);
                    }
                    return SynchronousFuture<bool>(true);
                  },
                  onEventTap: (CalendarEvent event, Rect rect) async {
                    _event = event;
                    _eventRect = rect;

                    if (MediaQuery.sizeOf(context).width <= 600) {
                      await showDialog(
                        context: context,
                        builder: (BuildContext context) {
                          return AlertDialog(
                            content: Column(
                              mainAxisSize: MainAxisSize.min,
                              crossAxisAlignment: CrossAxisAlignment.end,
                              children: [
                                IconButton(
                                  onPressed: () {
                                    final CalendarEvent? event = _event;
                                    setState(() {
                                      _events = [..._events]..remove(event);
                                    });
                                    _event = null;
                                    _eventRect = null;
                                    Navigator.pop(context);
                                  },
                                  icon: const Icon(Icons.delete),
                                ),
                                Column(
                                  crossAxisAlignment: CrossAxisAlignment.start,
                                  children: [
                                    const SizedBox(height: 8.0),
                                    Text(
                                        'Start: ${_event!.start.toIso8601String()}'),
                                    const SizedBox(height: 8.0),
                                    Text(
                                        'End: ${_event!.end.toIso8601String()}'),
                                    const SizedBox(height: 8.0),
                                    Text(
                                      'Duration: ${_event!.end.difference(_event!.start)}',
                                    ),
                                  ],
                                )
                              ],
                            ),
                          );
                        },
                      );
                      return;
                    }

                    _overlayController.show();
                  },
                  dayHeaderBuilder: (DateTime date) {
                    final dayWidget = Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text(
                        DateFormat('dd').format(date),
                        textAlign: TextAlign.center,
                      ),
                    );
                    return Center(
                      child: Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: Column(
                          children: [
                            Text(
                              DateFormat('EEEE').format(date),
                              textAlign: TextAlign.center,
                            ),
                            if (date.isToday())
                              DecoratedBox(
                                decoration: const BoxDecoration(
                                  shape: BoxShape.circle,
                                  color: Colors.purple,
                                ),
                                child: dayWidget,
                              )
                            else
                              dayWidget
                          ],
                        ),
                      ),
                    );
                  },
                  timeHeaderBuilder: (DateTime date) {
                    return Padding(
                      padding: const EdgeInsets.all(8.0),
                      child: Text(
                        DateFormat('HH:mm').format(date),
                        textAlign: TextAlign.center,
                      ),
                    );
                  },
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

说明

  1. 导入依赖
    • 确保你的 pubspec.yaml 文件中包含 p_calendar 依赖项。
    dependencies:
      flutter:
        sdk: flutter
      p_calendar: ^版本号
    

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

1 回复

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


p_calendar 是一个 Flutter 日历插件,它提供了丰富的日历功能,例如选择日期、自定义样式、事件标记等。下面是一个简单的示例,展示如何使用 p_calendar 插件来创建一个日历,并实现基本的日期选择功能。

1. 添加依赖

首先,在 pubspec.yaml 文件中添加 p_calendar 依赖:

dependencies:
  flutter:
    sdk: flutter
  p_calendar: ^0.0.1  # 请使用最新版本

然后运行 flutter pub get 来安装依赖。

2. 基本使用

lib/main.dart 文件中,使用 p_calendar 插件来创建一个简单的日历:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Calendar Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: CalendarPage(),
    );
  }
}

class CalendarPage extends StatefulWidget {
  @override
  _CalendarPageState createState() => _CalendarPageState();
}

class _CalendarPageState extends State<CalendarPage> {
  DateTime? _selectedDate;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Calendar Demo'),
      ),
      body: Column(
        children: [
          PCAlendar(
            onDaySelected: (DateTime date) {
              setState(() {
                _selectedDate = date;
              });
            },
          ),
          SizedBox(height: 20),
          Text(
            _selectedDate == null
                ? 'No date selected'
                : 'Selected Date: ${_selectedDate!.toLocal()}',
            style: TextStyle(fontSize: 18),
          ),
        ],
      ),
    );
  }
}

3. 运行应用

运行应用后,你会看到一个日历界面。当你选择一个日期时,选中的日期会显示在日历下方。

4. 自定义日历样式

p_calendar 允许你自定义日历的样式。例如,你可以自定义选中的日期的背景颜色、文本颜色等。以下是一个自定义样式的示例:

PCAlendar(
  onDaySelected: (DateTime date) {
    setState(() {
      _selectedDate = date;
    });
  },
  selectedDayTextStyle: TextStyle(color: Colors.white),
  selectedDayDecoration: BoxDecoration(
    color: Colors.blue,
    shape: BoxShape.circle,
  ),
  todayTextStyle: TextStyle(color: Colors.red),
  todayDecoration: BoxDecoration(
    color: Colors.yellow,
    shape: BoxShape.circle,
  ),
  weekendTextStyle: TextStyle(color: Colors.purple),
  weekdaysTextStyle: TextStyle(color: Colors.green),
  weekdaysHeight: 40,
  weekdaysPadding: EdgeInsets.all(8),
);

5. 事件标记

你还可以在日历中标记特定日期的事件。例如:

PCAlendar(
  onDaySelected: (DateTime date) {
    setState(() {
      _selectedDate = date;
    });
  },
  eventDates: {
    DateTime(2023, 10, 1): 'Event 1',
    DateTime(2023, 10, 15): 'Event 2',
  },
  eventTextStyle: TextStyle(color: Colors.red),
  eventDecoration: BoxDecoration(
    color: Colors.yellow,
    shape: BoxShape.circle,
  ),
);
回到顶部