搜尋套件、儲存庫與 APP 分類

m_calendar 封面
套件詳情v1.5.1

m_calendar

一個可自定義且輕量級的 Flutter 日曆小部件套件,支援基於天數和清單的選擇,並支援使用者定義的裝飾。

AndroidiOSWebmacOSWindowsLinux
近 30 天下載
226
喜歡數
12
Pub 分數
160

安裝套件

flutter pub add m_calendar

套件介紹

根據套件公開資料產生的選型評估,僅供參考。

m_calendar 是一個輕量級且高度可自訂的 Flutter 套件,用於建立日曆小工具。它支援基於天數和基於清單的選擇模式,讓使用者可以選擇日期或以結構化清單形式檢視事件。開發者可以為日期套用自訂裝飾,例如顏色、圖示或徽章,以強調特定日期。此套件設計簡單且在所有主要平台上表現高效,包括 Android、iOS、Web、macOS、Windows 和 Linux。非常適合需要直覺式日期選擇與視覺自訂功能的應用程式。

適用情境

  • 日曆選擇器在日程安排應用中
  • 帶有高亮日期的活動規劃
  • 用戶生日或紀念日追蹤
  • 生產力工具中的日曆視圖
  • 自定日期選擇介面

主要特色

  • 支援日期與清單選擇模式
  • 可擴充的日期裝飾系統
  • 輕量且高效
  • 跨平台相容性
  • 與 Flutter UI 的輕鬆整合
  • 可自訂的外觀與行為

適合對象

  • 開發日曆功能的開發者
  • 需要日期選擇並帶有視覺反饋的應用程式
  • 依賴項最少的專案
  • 跨平台日曆實作
  • 希望靈活日期樣式的UI設計師

使用建議

  • 有限的內建事件管理
  • 無原生重複事件支援
  • 需要手動處理日期邏輯
  • 最適合簡單到中等複雜度的日曆
  • 不適合用於功能完整的日曆應用程式與進階排程

原始 README

以下為關聯原始碼儲存庫的 README 原文快照,最新內容請造訪原始碼儲存庫。

📆 m_calendar

A customizable, lightweight Flutter calendar package with monthly, weekly, and horizontal layouts, single/range date selection, marked dates, and a built-in month/year picker.

pub.dev license


🖼️ Previews

Monthly range selection Monthly single selection Weekly calendar

Horizontal calendar

✨ Features

Feature Monthly Weekly Horizontal
Single-date selection ✅ ✅ ✅
Range selection ✅ — —
Marked dates ✅ — ✅
Month/year picker ✅ — ✅
Custom cell decoration ✅ ✅ ✅
Custom cell widget ✅ ✅ ✅
Auto-scroll to date — — ✅

📝 Behavioral notes

  • Monthly and weekly views start the week on Saturday by default. Pass startDay (e.g. Day.monday) to change it.
  • The weekly view reports a single picked date per tap (the first day of the selected week). The isRangeSelection flag is accepted for API symmetry, but weekly range selection is not implemented yet.
  • The horizontal view is always single-select, and selectedDay is initialized to today before any interaction — so controller.selectedDates returns [today] until the user (or controller) picks another date.
  • A cross-month range set via controller.selectRange(start, end) is truncated to the end of the start month.
  • Attach one MCalendarController per MCalendar widget. Attaching the same controller to multiple calendars is not supported.

🚀 Installation

Add to your pubspec.yaml:

dependencies:
  m_calendar: ^1.4.0

Then run:

flutter pub get

Import in your Dart file:

import 'package:m_calendar/m_calendar.dart';

📖 Usage

Monthly calendar (default)
MCalendar(
  selectedMonth: DateTime.now(),
  onUserPicked: (List<DateTime> dates) {
    // Single selection → list contains one date.
    print(dates);
  },
)
Monthly calendar with range selection
MCalendar(
  selectedMonth: DateTime.now(),
  isRangeSelection: true,
  onUserPicked: (List<DateTime> dates) {
    // Range → list contains every date in the selected range.
    // The callback fires only once both range endpoints are picked.
    print('From ${dates.first} to ${dates.last}');
  },
)
Monthly calendar with marked dates
MCalendar(
  selectedMonth: DateTime.now(),
  markedDaysList: [
    MarkedDaysModel(
      selectedDateList: [
        DateTime(2025, 6, 10),
        DateTime(2025, 6, 15),
      ],
      decoration: BoxDecoration(
        color: Colors.blue.withOpacity(0.3),
        shape: BoxShape.circle,
      ),
    ),
  ],
  onUserPicked: (dates) => print(dates),
)
Monthly calendar with month/year picker
MCalendar(
  selectedMonth: DateTime.now(),
  showMonthYearPicker: true, // tap the header to open the picker
  onUserPicked: (dates) => print(dates),
)

Note: MCalendar.monthly(...) defaults showMonthYearPicker to true.
The MCalendar(...) shorthand defaults it to false. Explicitly set it to your preference.

Programmatic control with MCalendarController

Control navigation and programmatic selections from outside the calendar:

final controller = MCalendarController();

MCalendar(
  controller: controller,
  selectedMonth: DateTime(2026, 3),
  onUserPicked: (dates) => print(dates),
)

// Imperative controls:
controller.nextMonth();
controller.previousMonth();
controller.setMonth(DateTime(2026, 6));
controller.selectDate(DateTime(2026, 6, 15));
controller.selectRange(DateTime(2026, 6, 10), DateTime(2026, 6, 18));
controller.clearSelection();

// Query selection:
print(controller.selectedDates);
Custom day cell builder (dayBuilder)

Render custom widgets with event dots, badges, or icons inside day cells:

MCalendar(
  selectedMonth: DateTime.now(),
  dayBuilder: (context, date, state) {
    if (state.isDisabled) {
      return Center(child: Text('${date.day}', style: const TextStyle(color: Colors.grey)));
    }
    if (state.isToday) {
      return Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Text('${date.day}', style: const TextStyle(fontWeight: FontWeight.bold)),
          const Icon(Icons.circle, size: 4, color: Colors.blue),
        ],
      );
    }
    return null; // return null to fall back to default rendering
  },
  onUserPicked: (dates) => print(dates),
)
Disabling dates (minDate, maxDate, isDateDisabled)

Prevent selection of past dates, weekends, or specific days:

MCalendar(
  selectedMonth: DateTime.now(),
  minDate: DateTime.now(), // past dates disabled
  maxDate: DateTime.now().add(const Duration(days: 60)), // max 60 days ahead
  isDateDisabled: (date) => date.weekday == DateTime.sunday, // disable Sundays
  disabledDecoration: BoxDecoration(
    color: Colors.grey.shade200,
    borderRadius: BorderRadius.circular(6),
  ),
  onUserPicked: (dates) => print(dates),
)
Animations (opt-in)

Animations are off by default to keep existing behavior unchanged. Pass a [CalendarAnimations] configuration to any calendar to enable them:

MCalendar(
  selectedMonth: DateTime.now(),
  animations: const CalendarAnimations(), // opt in
  onUserPicked: (dates) => print(dates),
)

This animates:

  • selection / range highlight changes on monthly, weekly, and horizontal cells, plus the month-picker tiles,
  • a fade + directional slide when the displayed month changes,
  • a smooth grid resize between months with different row counts.

Tune the timings and curves, or disable specific parts:

const CalendarAnimations(
  selectionDuration: Duration(milliseconds: 150),
  monthTransitionDuration: Duration(milliseconds: 250),
  sizeDuration: Duration.zero, // keep the height change instant
)

MCalendar(
  selectedMonth: DateTime.now(),
  animations: CalendarAnimations.none, // explicitly disable
  onUserPicked: (dates) => print(dates),
)

Animations automatically become instant when the platform requests reduced motion (MediaQueryData.disableAnimations).


Weekly calendar
MCalendar.weekly(
  selectedMonth: DateTime.now(),
  startDay: Day.sunday, // first day of the week; defaults to Day.saturday
  onUserPicked: (List<DateTime> dates) {
    // Returns [firstDayOfSelectedWeek].
    print(dates);
  },
)

The weekly view shows a scrollable table with one row per month (last 6 months) and up to 6 week columns. Months that span 6 calendar weeks (e.g. September 2023 with a Saturday week start) are displayed correctly without data loss.


Horizontal calendar
// Wrap in a SizedBox or Expanded to give the view a bounded height.
SizedBox(
  width: double.infinity,
  height: 120,
  child: MCalendar.horizontal(
    selectedMonth: DateTime.now(),
    onUserPicked: (DateTime date) {
      // Always a single date.
      print(date);
    },
  ),
)

The horizontal view callback is void Function(DateTime) — not a List — because the horizontal view only supports single-date selection.

Horizontal calendar with date range and auto-scroll
SizedBox(
  width: double.infinity,
  height: 120,
  child: MCalendar.horizontal(
    selectedMonth: DateTime.now(),
    initialDate: DateTime.now(),                       // scroll-to target
    endDate: DateTime.now().add(const Duration(days: 30)), // last selectable day
    autoScroll: true,                                  // default: true
    onUserPicked: (date) => print(date),
  ),
)

initialDate is also the first selectable date. Dates before initialDate and after endDate are shown greyed-out and cannot be tapped.

Assertion: passing initialDate after endDate throws an assertion error in debug mode with a clear message.


🎨 Styling

Cell decoration & child
MCalendar(
  selectedMonth: DateTime.now(),
  // Default (unselected) cell decoration
  decoration: BoxDecoration(
    color: Colors.grey.shade100,
    borderRadius: BorderRadius.circular(8),
  ),
  // Selected cell decoration
  userPickedDecoration: BoxDecoration(
    color: Colors.teal,
    borderRadius: BorderRadius.circular(8),
  ),
  // Custom widget inside the selected cell (overrides the default day number)
  userPickedChild: const Icon(Icons.check, color: Colors.white),
  // Cell padding
  cellPadding: const EdgeInsets.all(8),
  onUserPicked: (dates) => print(dates),
)
Header & week-label styling
MCalendar(
  selectedMonth: DateTime.now(),
  showMonthYearPicker: true,
  // Header text (month + year label)
  weekNameHeaderStyle: const TextStyle(
    fontWeight: FontWeight.bold,
    color: Colors.indigo,
  ),
  onUserPicked: (dates) => print(dates),
)
Month/year picker customization (horizontal view example)
MCalendar.horizontal(
  selectedMonth: DateTime.now(),
  showMonthYearPicker: true,
  monthYearPickerSelectedMonthColor: Colors.teal,
  monthYearPickerUnselectedMonthColor: Colors.grey.shade200,
  monthYearPickerCrossAxisCount: 4,
  monthYearPickerChildAspectRatio: 2.0,
  onUserPicked: (date) => print(date),
)

📦 Public API

MCalendar (monthly, default)
Parameter Type Default Description
selectedMonth DateTime required Month to display. Day/time components are ignored.
onUserPicked void Function(List<DateTime>) required Fires with selected dates.
isRangeSelection bool false Enables range selection.
showMonthYearPicker bool false Shows the month/year picker header.
markedDaysList List<MarkedDaysModel>? null Dates to visually mark.
decoration BoxDecoration? null Default cell decoration.
userPickedDecoration BoxDecoration? null Selected-cell decoration.
userPickedChild Widget? null Widget inside the selected cell.
defaultChild Widget? null Widget inside unselected cells.
cellPadding EdgeInsets? null Padding inside each cell.
controller MCalendarController? null Controller for programmatic navigation and selection.
dayBuilder CalendarDayBuilder? null Custom widget builder for day cells.
minDate DateTime? null Earliest selectable date.
maxDate DateTime? null Latest selectable date.
isDateDisabled bool Function(DateTime)? null Predicate to disable specific dates.
disabledDecoration BoxDecoration? null Decoration for disabled day cells.
disabledTextStyle TextStyle? null Text style for disabled day cell numbers.
startDay Day Day.saturday First day of the week; changes cell alignment and header labels.
animations CalendarAnimations? null Opt-in animation config; null = instant updates.
MCalendar.weekly

Same as above plus:

Parameter Type Default Description
startDay Day Day.saturday First day of the week.
MCalendar.horizontal
Parameter Type Default Description
selectedMonth DateTime required Month to display.
onUserPicked void Function(DateTime) required Fires with selected date.
initialDate DateTime? null First selectable date; auto-scroll target.
endDate DateTime? null Last selectable date.
autoScroll bool true Scroll to initialDate on first build.
showMonthYearPicker bool false Shows the month/year picker header.
showWeekDays bool true Show weekday labels under dates.
markedDaysList List<MarkedDaysModel>? null Dates to visually mark.
decoration BoxDecoration? null Default cell decoration.
userPickedDecoration BoxDecoration? null Selected-cell decoration.
headerHeight double? 320 Height of the picker bottom sheet.
headerIconColor Color? null Color for header arrow icons.
headerTextStyle TextStyle? null Style for header month/year text.
dateTextStyle TextStyle? null Style for the day number.
weekDaysTextStyle TextStyle? null Style for the weekday label.
selectedDateTextStyle TextStyle? null Day number style when selected.
selectedWeekDaysTextStyle TextStyle? null Weekday label style when selected.
animations CalendarAnimations? null Opt-in animation config; null = instant updates.
MarkedDaysModel
MarkedDaysModel(
  selectedDateList: [DateTime(2025, 6, 10)],
  decoration: BoxDecoration(color: Colors.amber, shape: BoxShape.circle),
  child: const Icon(Icons.star, size: 12), // optional overlay widget
)
Day enum

Used for MCalendar.weekly(startDay: ...):

Day.monday  Day.tuesday  Day.wednesday  Day.thursday
Day.friday  Day.saturday  Day.sunday
CalendarAnimations
Field Default Description
selectionDuration 180ms Duration of selection/range highlight transitions.
selectionCurve Curves.easeOut Curve for selection transitions.
monthTransitionDuration 250ms Duration of the month-change fade + slide.
monthTransitionCurve Curves.easeOutCubic Curve for the month-change transition.
sizeDuration 200ms Duration of grid resizes between row counts.

CalendarAnimations.none disables all of them. Any duration is forced to Duration.zero when the platform requests reduced motion.


⚠️ Migration Guide

1.3.x → 1.4.0

No breaking changes. All existing code continues to work.

Typo fix — provider class names (non-breaking, additive)

The internal providers were misspelled. The corrected names are now exported:

Old name (deprecated) New name
MonthlyCalenderTableProvider MonthlyCalendarTableProvider
WeeklyCalenderTableProvider WeeklyCalendarTableProvider

The old names still compile (they are @Deprecated typedef aliases) but will be removed in a future major release. Migrate by renaming:

// Before
MonthlyCalenderTableProvider()

// After
MonthlyCalendarTableProvider()
Barrel exports (non-breaking, additive)

MarkedDaysModel and Day are now re-exported from m_calendar.dart. You can remove separate imports if you had them:

// Before
import 'package:m_calendar/m_calendar.dart';
import 'package:m_calendar/model/marked_date_model.dart';
import 'package:m_calendar/provider/weekly_calendar_table_provider.dart';

// After
import 'package:m_calendar/m_calendar.dart'; // MarkedDaysModel and Day included

🔍 Frequently Asked Questions

Q: Why does the range callback fire only when both endpoints are selected?
A: The callback fires once the range is complete (two taps). During the first tap, rangeStart is set internally; the callback fires on the second tap with the full sorted date list.

Q: What happens if I tap the same date twice in range mode?
A: A single-date range is returned: [date].

Q: What happens if I tap a third date after completing a range?
A: The old range is cleared and a new range starts from the third tap.

Q: Can I use DateTime values with time components for markedDaysList?
A: Yes. Date matching always ignores the time component — only year, month, and day are compared.

Q: Can I show more than one decoration model on the same date?
A: The first matching MarkedDaysModel wins. Ensure no two models share the same date if you want distinct decorations.

Q: Why does MCalendar.weekly show up to 6 week columns?
A: Depending on startDay, some months span 6 calendar weeks (e.g. September 2023 with a Saturday start). Previous versions silently dropped the 6th week; 1.4.0 renders it correctly.


📄 License

MIT © Muntasir Asif

所屬分類

依賴

依賴清單 2 項

原始碼儲存庫

使用此套件的專案

暫無相關資料。