Flutter日历日视图插件calendar_day_view的使用

Flutter日历日视图插件calendar_day_view的使用

calendar_day_view 是一个功能强大且高度可定制的日历日视图库,它专注于提供良好的日视图体验。尽管有很多日历包可供选择,但它们并不都能很好地支持日视图。calendar_day_view 并不是要取代其他日历组件,而是为了补充现有组件,使其在应用中表现得更好。

主要特性

  • 类别溢出日视图(Category Overflow Day View):将一天分为多个类别,并且事件可以在同一类别内跨越不同的时间槽显示。
  • 类别日视图(Category Day View):展示一天内带有多个类别的事件。
  • 溢出日视图(Over flow Day View):类似传统日历,事件根据其持续时间扩展显示在多个时间行上。
  • 行内日视图(In Row Day View):在同一时间窗口内显示所有事件。
  • 事件日视图(Event Day View):仅展示当天的所有事件及其开始时间。
  • 支持自定义一天的开始和结束时间、时间间隔(单行代表的时间长度)、是否显示当前时间线等。
  • 用户可以点击日视图以执行特定操作(例如,在指定时间创建事件)。

安装与导入库

pubspec.yaml 文件中添加依赖项:

dependencies:
  calendar_day_view: ^latest_version

然后在需要使用的文件中导入:

import 'package:calendar_day_view/calendar_day_view.dart';

使用示例

以下是一个完整的示例应用程序,演示了如何使用 calendar_day_view 插件中的不同类型的日视图。

示例代码

import 'dart:math';

import 'package:calendar_day_view/calendar_day_view.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:faker/faker.dart';

final timeFormat = DateFormat('ha');
final rd = Random();

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.white)),
      home: const CalendarDayViewExample(),
    );
  }
}

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

  @override
  State<CalendarDayViewExample> createState() => _CalendarDayViewExampleState();
}

class _CalendarDayViewExampleState extends State<CalendarDayViewExample> {
  List<DayEvent<String>> dayEvents = fakeEvents().map((e) => e.copyWith(end: e.start.add(Duration(minutes: faker.randomGenerator.element([20, 30, 90, 60]))))).toList();
  List<EventCategory> categories = [
    EventCategory(id: "1", name: "cate 1"),
    EventCategory(id: "2", name: "cate 2"),
    EventCategory(id: "3", name: "cate 3"),
    EventCategory(id: "4", name: "cate 4"),
  ];
  List<CategorizedDayEvent<String>> categoryEvents = genEvents(categories.length);

  void addCategory() {
    setState(() {
      categories.add(EventCategory(id: "${categories.length + 1}", name: "cate ${categories.length + 1}"));
      categoryEvents = genEvents(categories.length);
    });
  }

  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    final bodyItems = [
      // OverflowDayViewTab 实现
      OverflowDayViewTab(
        events: dayEvents,
        onTimeTap: (time) {
          setState(() {
            dayEvents = [
              ...dayEvents,
              DayEvent(
                value: faker.conference.name(),
                start: time,
                end: time.add(Duration(minutes: faker.randomGenerator.element([20, 140]))),
              )
            ];
          });
        },
      ),
      // CategoryOverflowDayViewTab 实现
      CategoryOverflowDayViewTab(
        events: categoryEvents,
        categories: categories,
        addEventOnClick: (cate, time) {
          setState(() {
            categoryEvents = [
              ...categoryEvents,
              CategorizedDayEvent(
                  categoryId: cate.id,
                  value: faker.conference.name(),
                  start: time)
            ];
          });
        },
      ),
      // CategoryDayViewTab 实现
      CategoryDayViewTab(
        events: categoryEvents,
        categories: categories,
        addEventOnClick: (cate, time) {
          setState(() {
            categoryEvents = [
              ...categoryEvents,
              CategorizedDayEvent(
                  categoryId: cate.id,
                  value: faker.conference.name(),
                  start: time)
            ];
          });
        },
      ),
      // InRowDayViewTab 实现
      InRowDayViewTab(
        events: dayEvents,
      ),
      // EventDayViewTab 实现
      EventDayViewTab(events: dayEvents),
    ];

    return DefaultTabController(
      length: 5,
      child: SafeArea(
        child: Scaffold(
          extendBodyBehindAppBar: true,
          backgroundColor: Theme.of(context).colorScheme.background,
          bottomNavigationBar: BottomNavigationBar(
            showUnselectedLabels: true,
            showSelectedLabels: true,
            unselectedFontSize: 14,
            unselectedItemColor: Colors.black,
            selectedItemColor: Colors.blue,
            items: const [
              BottomNavigationBarItem(icon: Icon(Icons.calendar_month), label: "Overflow"),
              BottomNavigationBarItem(icon: Icon(Icons.calendar_view_day), label: "Category Overflow"),
              BottomNavigationBarItem(icon: Icon(Icons.calendar_view_day), label: "Category"),
              BottomNavigationBarItem(icon: Icon(Icons.calendar_today_outlined), label: "In Row"),
              BottomNavigationBarItem(icon: Icon(Icons.calendar_view_month), label: "Events"),
            ],
            onTap: (value) => setState(() => currentIndex = value),
            currentIndex: currentIndex,
          ),
          appBar: AppBar(
            title: Text(
              "${getTitle(currentIndex)} - ${currentIndex == 1 || currentIndex == 2 ? categoryEvents.length : dayEvents.length} events",
              style: const TextStyle(color: Colors.teal, fontSize: 30),
            ),
            toolbarHeight: 100,
            centerTitle: false,
            actions: [
              Row(
                children: [
                  if (currentIndex == 1 || currentIndex == 2)
                    Row(
                      children: [
                        TextButton.icon(
                          style: TextButton.styleFrom(backgroundColor: Colors.white),
                          onPressed: () => setState(() => categoryEvents = genEvents(categories.length)),
                          icon: const Icon(Icons.refresh),
                          label: const Text("events"),
                        ),
                        const SizedBox(width: 10),
                        TextButton.icon(
                          style: TextButton.styleFrom(backgroundColor: Colors.white),
                          onPressed: addCategory,
                          icon: const Icon(Icons.add),
                          label: const Text("category"),
                        ),
                      ],
                    ),
                  else
                    TextButton.icon(
                      style: TextButton.styleFrom(backgroundColor: Colors.white),
                      onPressed: () => setState(() => dayEvents = fakeEvents()),
                      icon: const Icon(Icons.refresh),
                      label: const Text("events"),
                    ),
                  const SizedBox(width: 10),
                ],
              )
            ],
          ),
          body: bodyItems[currentIndex],
        ),
      ),
    );
  }
}

String getTitle(int index) {
  switch (index) {
    case 0:
      return "Overflow Day View";
    case 1:
      return "Category Overflow Day View";
    case 2:
      return "Category Day View";
    case 3:
      return "In Row Day View";
    case 4:
      return "Events Day View";
    default:
      return "Calendar Day View";
  }
}

List<DayEvent<String>> fakeEvents() => faker.randomGenerator.amount((i) {
      final start = DateTime.now().copyWith(
        hour: faker.randomGenerator.integer(19, min: 5),
        minute: faker.randomGenerator.element([0, 10, 20, 40]),
        second: 0,
      );
      return DayEvent(
        value: faker.conference.name(),
        start: start,
        end: start.add(Duration(minutes: faker.randomGenerator.element([20, 30, 90, 60]))),
      );
    }, 30, min: 10);

List<CategorizedDayEvent<String>> genEvents(int categoryLength) => faker.randomGenerator.amount(
      (i) {
        final hour = faker.randomGenerator.integer(17, min: 7);
        final start = DateTime.now().copyWith(
          hour: hour,
          minute: faker.randomGenerator.element([0, 15, 20]),
          second: 0,
        );
        return CategorizedDayEvent(
            categoryId: faker.randomGenerator.integer(categoryLength + 1, min: 1).toString(),
            value: faker.conference.name(),
            start: start,
            end: start.add(Duration(minutes: faker.randomGenerator.element([90, 60])));
      },
      categoryLength * 5,
      min: 10,
    );

// 溢出日视图标签页
class OverflowDayViewTab extends StatelessWidget {
  final List<DayEvent<String>> events;
  final Function(TimeOfDay) onTimeTap;

  const OverflowDayViewTab({required this.events, required this.onTimeTap, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CalendarDayView.overflow(
      config: OverFlowDayViewConfig(
        currentDate: DateTime.now(),
        timeGap: 60,
        heightPerMin: 2,
        endOfDay: const TimeOfDay(hour: 20, minute: 0),
        startOfDay: const TimeOfDay(hour: 4, minute: 0),
        renderRowAsListView: true,
        time12: true,
      ),
      onTimeTap: (t) {
        onTimeTap(t);
      },
      events: UnmodifiableListView(events),
      overflowItemBuilder: (context, constraints, itemIndex, event) {
        return Container(
          color: getRandomColor(),
          padding: const EdgeInsets.all(8.0),
          child: Text(event.value),
        );
      },
    );
  }
}

// 类别溢出日视图标签页
class CategoryOverflowDayViewTab extends StatelessWidget {
  final List<CategorizedDayEvent<String>> events;
  final List<EventCategory> categories;
  final Function(EventCategory, TimeOfDay) addEventOnClick;

  const CategoryOverflowDayViewTab({
    required this.events,
    required this.categories,
    required this.addEventOnClick,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CalendarDayView.category(
      config: CategoryDavViewConfig(
        time12: true,
        allowHorizontalScroll: true,
        columnsPerPage: 2,
        currentDate: DateTime.now(),
        timeGap: 60,
        heightPerMin: 1,
        evenRowColor: Colors.white,
        oddRowColor: Colors.grey[200],
        headerDecoration: BoxDecoration(color: Colors.lightBlueAccent.withOpacity(.5)),
        logo: const Padding(
          padding: EdgeInsets.all(8.0),
          child: CircleAvatar(child: Text("C")),
        ),
      ),
      categories: categories,
      events: events,
      onTileTap: (category, time) {
        addEventOnClick(category, time);
      },
      controlBarBuilder: (goToPreviousTab, goToNextTab) => Container(), // 自定义控制栏构建器
      eventBuilder: (constraints, category, _, event) {
        return Container(
          color: getRandomColor(),
          padding: const EdgeInsets.all(8.0),
          child: Text(event.value),
        );
      },
    );
  }
}

// 类别日视图标签页
class CategoryDayViewTab extends StatelessWidget {
  final List<CategorizedDayEvent<String>> events;
  final List<EventCategory> categories;
  final Function(EventCategory, TimeOfDay) addEventOnClick;

  const CategoryDayViewTab({
    required this.events,
    required this.categories,
    required this.addEventOnClick,
    Key? key,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CalendarDayView.category(
      config: CategoryDavViewConfig(
        time12: true,
        allowHorizontalScroll: true,
        columnsPerPage: 2,
        currentDate: DateTime.now(),
        timeGap: 60,
        heightPerMin: 1,
        evenRowColor: Colors.white,
        oddRowColor: Colors.grey[200],
        headerDecoration: BoxDecoration(color: Colors.lightBlueAccent.withOpacity(.5)),
        logo: const Padding(
          padding: EdgeInsets.all(8.0),
          child: CircleAvatar(child: Text("C")),
        ),
      ),
      categories: categories,
      events: events,
      onTileTap: (category, time) {
        addEventOnClick(category, time);
      },
      controlBarBuilder: (goToPreviousTab, goToNextTab) => Container(), // 自定义控制栏构建器
      eventBuilder: (constraints, category, _, event) {
        return Container(
          color: getRandomColor(),
          padding: const EdgeInsets.all(8.0),
          child: Text(event.value),
        );
      },
    );
  }
}

// 行内日视图标签页
class InRowDayViewTab extends StatelessWidget {
  final List<DayEvent<String>> events;

  const InRowDayViewTab({required this.events, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CalendarDayView<String>.inRow(
      config: InRowDayViewConfig(
        heightPerMin: 1,
        showCurrentTimeLine: true,
        dividerColor: Colors.black,
        timeGap: 60,
        showWithEventOnly: false,
        currentDate: DateTime.now(),
        startOfDay: TimeOfDay(hour: 3, minute: 00),
        endOfDay: TimeOfDay(hour: 22, minute: 00),
      ),
      events: UnmodifiableListView(events),
      itemBuilder: (context, constraints, event) => Flexible(
        child: Container(
          color: getRandomColor(),
          padding: const EdgeInsets.all(8.0),
          child: Text(event.value),
        ),
      ),
    );
  }
}

// 事件日视图标签页
class EventDayViewTab extends StatelessWidget {
  final List<DayEvent<String>> events;

  const EventDayViewTab({required this.events, Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CalendarDayView.eventOnly(
      config: EventDayViewConfig(
        showHourly: true,
        currentDate: DateTime.now(),
      ),
      events: events,
      eventDayViewItemBuilder: (context, index, event) {
        return Container(
          color: getRandomColor(),
          height: 50,
          child: Text(event.value),
        );
      },
    );
  }
}

Color getRandomColor() {
  return Color((Random().nextDouble() * 0xFFFFFF).toInt()).withOpacity(1.0);
}

以上代码展示了如何使用 calendar_day_view 插件的不同视图类型,并提供了交互功能,如添加事件、刷新数据等。你可以根据自己的需求调整配置参数和样式。希望这个示例能帮助你更好地理解和使用 calendar_day_view 插件!


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

1 回复

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


当然,以下是如何在Flutter项目中使用calendar_day_view插件来创建一个日历日视图的一个示例代码。这个示例将展示如何集成该插件并显示一个简单的日视图日历。

首先,确保你已经在pubspec.yaml文件中添加了calendar_day_view依赖:

dependencies:
  flutter:
    sdk: flutter
  calendar_day_view: ^最新版本号  # 请替换为实际的最新版本号

然后,运行flutter pub get来获取依赖。

接下来,在你的Flutter项目中,你可以创建一个简单的页面来展示日历日视图。以下是一个完整的示例代码:

import 'package:flutter/material.dart';
import 'package:calendar_day_view/calendar_day_view.dart';
import 'package:intl/intl.dart';

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

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

class CalendarDayViewPage extends StatefulWidget {
  @override
  _CalendarDayViewPageState createState() => _CalendarDayViewPageState();
}

class _CalendarDayViewPageState extends State<CalendarDayViewPage> {
  DateTime _selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Calendar Day View Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              'Selected Date: ${DateFormat('yyyy-MM-dd').format(_selectedDate)}',
              style: TextStyle(fontSize: 20),
            ),
            SizedBox(height: 16),
            Expanded(
              child: CalendarDayView(
                initialDate: _selectedDate,
                firstDayOfWeek: DateTime.monday,
                onDateSelected: (DateTime date) {
                  setState(() {
                    _selectedDate = date;
                  });
                },
                builder: (context, date, events) {
                  // 这里你可以自定义每一天的视图
                  return Container(
                    decoration: BoxDecoration(
                      color: date == _selectedDate ? Colors.blue.withOpacity(0.1) : Colors.transparent,
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Center(
                      child: Text(
                        DateFormat('EEE, MMM d').format(date),
                        style: TextStyle(color: Colors.black, fontSize: 18),
                      ),
                    ),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

代码解释:

  1. 依赖导入:首先导入必要的包,包括flutter/material.dartcalendar_day_view/calendar_day_view.dartintl/intl.dart用于日期格式化。

  2. 应用入口MyApp类作为应用的入口,设置了应用的主题和主页面。

  3. 主页面CalendarDayViewPage是一个有状态的Widget,用于管理日历的选中日期。

  4. 状态管理:在_CalendarDayViewPageState中,有一个_selectedDate变量来存储当前选中的日期。

  5. UI布局

    • 使用ScaffoldAppBar来创建应用的顶部导航栏。
    • 使用Column来垂直排列文本和日历视图。
    • 文本显示当前选中的日期。
    • Expanded widget确保日历视图可以扩展以填充剩余的空间。
  6. 日历视图CalendarDayView widget用于显示日历日视图。

    • initialDate设置初始日期。
    • firstDayOfWeek设置一周的第一天。
    • onDateSelected回调用于处理日期选择事件。
    • builder函数用于自定义每一天的视图,这里简单地根据是否选中日期来改变背景色和显示日期。

运行这个代码,你将看到一个简单的日历日视图,可以滚动选择不同的日期,并且选中的日期会在顶部文本中显示。你可以根据需要进一步自定义和扩展这个示例。

回到顶部