vn_date_time_picker
A fully customizable date, time, date-time, and date range picker for Flutter — with Vietnamese & English support, year picker, haptic feedback, responsive portrait/landscape layout, and complete theming.
Features
- Date Picker — month calendar with infinite scroll, tap header to open year grid
- Time Picker — scroll wheel (12h / 24h), AM/PM toggle, haptic feedback (bật/tắt)
- Date-Time Picker — combined tab UI (Date | Time)
- Date Range Picker — Google Calendar-style band highlight, year grid
- i18n — built-in Vietnamese & English; extensible to any language
- Full theming — every color, radius, and text style is overridable
- Responsive — portrait and landscape layouts, state preserved on rotation
- Zero dependencies — only Flutter SDK
Screenshots
| Date Picker | Time Picker | Range Picker | Year Grid |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
Run
flutter runinsideexample/to see all pickers on a real device.
Installation
Add to your pubspec.yaml:
dependencies:
vn_date_time_picker: ^0.2.0
Then import:
import 'package:vn_date_time_picker/vn_date_time_picker.dart';
Quick Start
Date Picker
final date = await showCustomDatePicker(
context: context,
config: DatePickerConfig(
firstDate: DateTime(2020),
lastDate: DateTime(2030),
initialDate: DateTime.now(),
locale: PickerLocale.vi, // tiếng Việt
weekStartDay: WeekStartDay.monday,
dateFormatPattern: DateFormatPattern.ddMMMyyyy,
),
);
// date → DateTime? (null nếu người dùng bấm Hủy)
Time Picker
final time = await showCustomTimePicker(
context: context,
config: TimePickerConfig(
initialTime: TimeOfDay.now(),
use24HourFormat: false, // 12h với AM/PM
locale: PickerLocale.vi,
haptic: TimePickerHaptic.selectionClick, // rung nhẹ mỗi item (mặc định)
),
);
// time → TimeOfDay?
Date-Time Picker
final result = await showCustomDateTimePicker(
context: context,
dateConfig: DatePickerConfig(
firstDate: DateTime(2020),
lastDate: DateTime(2030),
locale: PickerLocale.vi,
),
timeConfig: TimePickerConfig(
use24HourFormat: false,
locale: PickerLocale.vi,
),
);
// result → DateTimePickerResult?
// result.date → DateTime
// result.time → TimeOfDay
// result.toDateTime() → DateTime (combined)
Date Range Picker
final range = await showCustomDateRangePicker(
context: context,
config: DateRangePickerConfig(
firstDate: DateTime(2020),
lastDate: DateTime(2030),
locale: PickerLocale.vi,
weekStartDay: WeekStartDay.monday,
),
);
// range → DateRange?
// range.start → DateTime
// range.end → DateTime
// range.dayCount → int (số ngày inclusive)
API Reference
DatePickerConfig
| Property | Type | Default | Description |
|---|---|---|---|
firstDate |
DateTime |
required | Ngày nhỏ nhất có thể chọn |
lastDate |
DateTime |
required | Ngày lớn nhất có thể chọn |
initialDate |
DateTime? |
DateTime.now() |
Ngày được chọn sẵn |
weekStartDay |
WeekStartDay |
.sunday |
Ngày đầu tuần |
dateFormatPattern |
DateFormatPattern |
.ddMMyyyy |
Định dạng ngày |
locale |
PickerLocale |
PickerLocale.vi |
Ngôn ngữ |
theme |
PickerThemeData? |
(app theme) | Giao diện |
TimePickerConfig
| Property | Type | Default | Description |
|---|---|---|---|
initialTime |
TimeOfDay? |
TimeOfDay.now() |
Giờ chọn sẵn |
use24HourFormat |
bool |
false |
Chế độ 24h |
locale |
PickerLocale |
PickerLocale.vi |
Ngôn ngữ |
theme |
PickerThemeData? |
(app theme) | Giao diện |
haptic |
TimePickerHaptic |
.selectionClick |
Kiểu rung khi xoay bánh xe |
TimePickerHaptic
TimePickerHaptic.none // Tắt rung hoàn toàn
TimePickerHaptic.selectionClick // Rung nhẹ kiểu iOS (mặc định)
TimePickerHaptic.lightImpact // Rung nhẹ hơn
TimePickerHaptic.mediumImpact // Rung vừa
Android: cần thêm vào
AndroidManifest.xml:<uses-permission android:name="android.permission.VIBRATE"/>iOS: không cần quyền gì thêm.
DateRangePickerConfig
| Property | Type | Default | Description |
|---|---|---|---|
firstDate |
DateTime |
required | Giới hạn nhỏ nhất |
lastDate |
DateTime |
required | Giới hạn lớn nhất |
initialRange |
DateRange? |
null |
Khoảng chọn sẵn |
weekStartDay |
WeekStartDay |
.sunday |
Ngày đầu tuần |
dateFormatPattern |
DateFormatPattern |
.ddMMyyyy |
Định dạng ngày |
locale |
PickerLocale |
PickerLocale.vi |
Ngôn ngữ |
theme |
PickerThemeData? |
(app theme) | Giao diện |
WeekStartDay
WeekStartDay.monday // Tuần bắt đầu từ Thứ 2
WeekStartDay.sunday // Tuần bắt đầu từ Chủ nhật
WeekStartDay.saturday // Tuần bắt đầu từ Thứ 7
// ... tuesday, wednesday, thursday, friday
DateFormatPattern
DateFormatPattern.ddMMyyyy // 15/06/2024
DateFormatPattern.MMddyyyy // 06/15/2024
DateFormatPattern.yyyyMMdd // 2024-06-15
DateFormatPattern.ddMMyyyy_dot // 15.06.2024
DateFormatPattern.MMMddyyyy // June 15, 2024
DateFormatPattern.ddMMMyyyy // 15 June 2024
DateRange
range.start // → DateTime (ngày bắt đầu)
range.end // → DateTime (ngày kết thúc)
range.dayCount // → int (số ngày inclusive)
Theming
Cách 1 — Dùng màu của app (tự động)
Nếu không truyền theme, picker sẽ tự lấy ColorScheme của MaterialApp:
MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
useMaterial3: true,
),
// ... picker sẽ tự dùng màu indigo
)
Cách 2 — Từ một màu seed
theme: PickerThemeData.fromSeed(Colors.teal),
Cách 3 — Tùy chỉnh hoàn toàn
theme: PickerThemeData(
// Màu cốt lõi
primary: const Color(0xFF006494),
onPrimary: Colors.white,
surface: const Color(0xFFF8F9FA),
onSurface: const Color(0xFF1C1B1F),
disabledColor: const Color(0xFFBDBDBD),
// Màu chi tiết (nullable — tự suy từ primary nếu null)
weekdayRowColor: const Color(0xFFE3F2FD), // nền hàng CN T2 T3…
rangeBandColor: const Color(0xFFBBDEFB), // dải khoảng range picker
todayBorderColor: Colors.orange, // viền ô hôm nay
// Hình dạng
dialogBorderRadius: 20.0,
selectedDayRadius: 8.0, // double.infinity = tròn hoàn toàn (mặc định)
// Override decoration ngày được chọn (tùy chọn)
selectedDayDecoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF006494), Color(0xFF0096C7)],
),
borderRadius: BorderRadius.circular(8),
),
// TextStyle
headerTitleStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.w800),
headerSubtitleStyle: const TextStyle(fontSize: 12),
weekdayStyle: const TextStyle(fontSize: 11, fontWeight: FontWeight.w700),
dayStyle: const TextStyle(fontSize: 13),
selectedDayStyle: const TextStyle(fontWeight: FontWeight.bold),
timeWheelStyle: const TextStyle(fontSize: 18, fontWeight: FontWeight.w300),
timeSelectedStyle: const TextStyle(fontSize: 26, fontWeight: FontWeight.bold),
)
Cách 4 — Override một phần với copyWith
theme: PickerThemeData.fromSeed(Colors.indigo).copyWith(
todayBorderColor: Colors.orange,
selectedDayRadius: 6,
weekdayRowColor: Colors.indigo.shade50,
),
Dark theme
theme: const PickerThemeData(
primary: Color(0xFF90CAF9),
onPrimary: Color(0xFF0D1B2A),
surface: Color(0xFF1E1E2E),
onSurface: Color(0xFFE0E0E0),
disabledColor: Color(0xFF4A4A5A),
),
Localization
Tiếng Việt (mặc định)
locale: PickerLocale.vi,
English
locale: PickerLocale.en,
Ngôn ngữ tùy chỉnh
locale: const PickerLocale(
monthNames: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
weekdayLabels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
cancelText: 'Cancel',
confirmText: 'OK',
selectDateTitle: 'Pick a date',
selectTimeTitle: 'Pick a time',
amLabel: 'AM',
pmLabel: 'PM',
dateTabLabel: 'Date',
timeTabLabel: 'Time',
selectRangeTitle: 'Select range',
startDateLabel: 'From',
endDateLabel: 'To',
),
Sử dụng widget trực tiếp (không dùng dialog)
// Nhúng trực tiếp vào UI
CustomDatePicker(
config: DatePickerConfig(
firstDate: DateTime(2020),
lastDate: DateTime(2030),
),
onDateSelected: (date) => setState(() => _date = date),
)
CustomTimePicker(
config: TimePickerConfig(
haptic: TimePickerHaptic.selectionClick,
),
onTimeSelected: (time) => setState(() => _time = time),
)
CustomDateRangePicker(
config: DateRangePickerConfig(
firstDate: DateTime(2020),
lastDate: DateTime(2030),
),
onRangeChanged: (range) => setState(() => _range = range),
)
Yêu cầu môi trường
| Yêu cầu | Phiên bản tối thiểu |
|---|---|
| Dart SDK | ≥ 3.0.0 |
| Flutter | ≥ 3.27.0 |
Changelog
Xem CHANGELOG.md.
License
MIT License — xem file LICENSE.



