Flutter连续日历插件continuous_calendar的使用

Flutter 连续日历插件 continuous_calendar 的使用

continuous_calendar

一个灵活的热图+选择器日历,可以显示多个月份,非常适合预订。

特性

目前仅支持水平布局。

开始使用

要开始使用此插件,请在 pubspec.yaml 文件中添加依赖项:

dependencies:
  continuous_calendar: ^x.x.x

然后运行 flutter pub get 命令以获取最新版本的包。

使用方法

完整的示例代码可以在 /example 文件夹中查看。以下是一个简单的示例,展示如何使用 continuous_calendar 插件。

示例代码
import 'package:continuous_calendar/continuous_calendar.dart';
import 'package:date_n_time/date_n_time.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

import 'src/material_drag_scroll_behavior.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Example App',
      theme: ThemeData(
        useMaterial3: true,
      ),
      home: const MyHome(),
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate
      ],
      supportedLocales: const [
        Locale('zh'), // 简体中文
      ],
    );
  }
}

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

  [@override](/user/override)
  State<MyHome> createState() => _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  // LocalDate displayedMonth = LocalDate.now().atStartOfMonth();
  CalendarPageController controller = CalendarPageController();

  [@override](/user/override)
  Widget build(BuildContext context) {
    final today = LocalDate.now();
    final firstDate = today.minus(2, ChronoUnit.months);
    final lastDate = today.plus(2, ChronoUnit.months);

    final availability = _generateAvailability();
    final colorMap = availability.map((key, value) =>
        MapEntry(
            key,
            switch (value) {
              Availability.available => _colorAvailable,
              Availability.blocked => _colorBlocked,
              Availability.booked => _colorBooked,
              Availability.unavailable => _colorUnavailable,
            }));

    return Scaffold(
      appBar: AppBar(
        title: const Text('Example App'),
        actions: [
          IconButton(
            onPressed: () => showDateRangePicker(
              context: context,
              firstDate: firstDate.atStartOfDay().atZone(ZoneId.system),
              lastDate: lastDate.atStartOfDay().atZone(ZoneId.system),
            ),
            icon: const Icon(Icons.calendar_month),
          ),
          IconButton(
            onPressed: () => showDatePicker(
              context: context,
              firstDate: firstDate.atStartOfDay().atZone(ZoneId.system),
              lastDate: lastDate.atStartOfDay().atZone(ZoneId.system),
            ),
            icon: const Icon(Icons.calendar_today),
          ),
        ],
      ),
      body: Column(
        children: [
          CalendarPageView(
            firstDate: firstDate,
            lastDate: lastDate,
            dayBackgroundColorMap: colorMap,
            rangeSelectionBackgroundColor: Colors.blue[200],
            scrollBehavior: MaterialDragScrollBehavior(),
            scrollDirection: Axis.horizontal,
            controller: controller,
            // onDisplayedMonthChanged: (date) => setState(() => displayedMonth = date),
            selectableDayPredicate:
                (date, selectedStartDate, selectedEndDate) =>
                    availability[date] == Availability.available,
          ),
          ListenableBuilder(
            listenable: controller,
            builder: (context, child) {
              final localizations = MaterialLocalizations.of(context);
              final monthText = localizations.formatMonthYear(
                  controller.value.atStartOfDay().atZone(ZoneId.utc));

              return Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  IconButton(
                    onPressed: controller.previousMonth,
                    icon: const Icon(Icons.chevron_left),
                  ),
                  Text(monthText),
                  IconButton(
                    onPressed: controller.nextMonth,
                    icon: const Icon(Icons.chevron_right),
                  ),
                  IconButton(
                    onPressed: () => controller
                        .toMonth(controller.value.plus(2, ChronoUnit.months)),
                    icon: const Icon(Icons.fast_forward),
                  )
                ],
              );
            },
          ),
        ],
      ),
    );
  }

  Map<LocalDate, Availability> _generateAvailability() {
    final locale = Localizations.localeOf(context);
    final today = LocalDate.now();
    final startOfWeek = today.atStartOfWeek(locale.toString());
    final endOfWeek = startOfWeek.plus(6, ChronoUnit.days);
    final startOfNearWeek = startOfWeek.minus(1, ChronoUnit.weeks);
    final endOfNearWeek = endOfWeek.plus(1, ChronoUnit.weeks);
    final startOfPrevMonth = today.minus(1, ChronoUnit.months).atStartOfMonth();
    final endOfNextMonth = today.plus(1, ChronoUnit.months).atEndOfMonth();
    final firstDate = today.minus(2, ChronoUnit.months);
    final lastDate = today.plus(2, ChronoUnit.months);

    final visibleRange = LocalDateRange(firstDate, lastDate);
    final totalRange = LocalDateRange(startOfPrevMonth, endOfNextMonth);
    final nearRange = LocalDateRange(startOfNearWeek, endOfNearWeek);
    final weekRange = LocalDateRange(startOfWeek, endOfWeek);

    var visibleMap = {
      for (var date in visibleRange.toLocalDates())
        date: Availability.unavailable
    };

    var totalMap = {
      for (var date in totalRange.toLocalDates()) date: Availability.available
    };

    var nearMap = {
      for (var date in nearRange.toLocalDates()) date: Availability.blocked
    };

    var weekMap = {
      for (var date in weekRange.toLocalDates()) date: Availability.booked
    };

    visibleMap
      ..addAll(totalMap)
      ..addAll(nearMap)
      ..addAll(weekMap);

    return visibleMap;
  }

  static const _colorAvailable = WidgetStateColor.fromMap({
    WidgetState.selected: Color(0xFF2E7D32),
    WidgetState.any: Color(0xFFA5D6A7),
  });
  static const _colorBlocked = WidgetStateColor.fromMap({
    WidgetState.selected: Color(0xFFEF6C00),
    WidgetState.any: Color(0xFFFFCC80),
  });
  static const _colorBooked = WidgetStateColor.fromMap({
    WidgetState.selected: Color(0xFFC62828),
    WidgetState.any: Color(0xFFEF9A9A),
  });
  static const _colorUnavailable = WidgetStateColor.fromMap({
    WidgetState.any: Color(0xFFE0E0E0),
  });
}

enum Availability {
  unavailable,
  booked,
  blocked,
  available;
}

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

1 回复

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


continuous_calendar 是一个用于 Flutter 的插件,它可以帮助你实现一个连续的、自定义的日历视图。这个插件支持手势滑动、日期选择、日期范围选择等功能,适用于需要展示连续月份的日历场景。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 continuous_calendar 插件的依赖:

dependencies:
  flutter:
    sdk: flutter
  continuous_calendar: ^1.0.0  # 请使用最新的版本号

然后运行 flutter pub get 来安装插件。

基本使用

以下是一个简单的使用 continuous_calendar 插件的示例:

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

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Continuous Calendar Example'),
        ),
        body: ContinuousCalendar(
          onMonthChanged: (DateTime date) {
            print("Month changed: $date");
          },
        ),
      ),
    );
  }
}

主要功能

  1. 连续滚动:默认情况下,ContinuousCalendar 支持连续滚动,用户可以通过左右滑动来浏览不同的月份。

  2. 日期选择:你可以通过 onDaySelected 回调来监听用户选择的日期。

    ContinuousCalendar(
      onDaySelected: (DateTime date) {
        print("Selected date: $date");
      },
    )
    
  3. 日期范围选择:你也可以通过 onRangeSelected 回调来监听用户选择的日期范围。

    ContinuousCalendar(
      onRangeSelected: (DateTime start, DateTime end) {
        print("Selected range: $start - $end");
      },
    )
    
  4. 自定义样式ContinuousCalendar 提供了多种自定义选项,如 calendarStyleheaderStyle,允许你自定义日历的外观。

    ContinuousCalendar(
      calendarStyle: CalendarStyle(
        selectedColor: Colors.blue,
        todayColor: Colors.green,
      ),
      headerStyle: HeaderStyle(
        titleTextStyle: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
      ),
    )
    
  5. 初始日期:你可以通过 initialDate 参数来设置日历的初始日期。

    ContinuousCalendar(
      initialDate: DateTime(2023, 10, 15),
    )
    

完整的示例

以下是一个更完整的示例,展示了如何使用 continuous_calendar 插件:

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

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  [@override](/user/override)
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  DateTime _selectedDate = DateTime.now();

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Continuous Calendar Example'),
      ),
      body: Column(
        children: [
          ContinuousCalendar(
            initialDate: DateTime(2023, 10, 15),
            onDaySelected: (DateTime date) {
              setState(() {
                _selectedDate = date;
              });
            },
            calendarStyle: CalendarStyle(
              selectedColor: Colors.blue,
              todayColor: Colors.green,
            ),
            headerStyle: HeaderStyle(
              titleTextStyle: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: Text(
              'Selected Date: $_selectedDate',
              style: TextStyle(fontSize: 18),
            ),
          ),
        ],
      ),
    );
  }
}
回到顶部