はじめに
とりあえずできる画面はこんな感じです。
Flutterのtable_calendarを使ってみる pic.twitter.com/scbyeu3XPE
— kokogento (@gento34165638) September 3, 2024
Riverpodの最初の設定部分は省いています
必要パッケージを入れる
まずは必要なパッケージを入れます。
riverpodはいくつか種類ありますがflutter_riverpod
を選びます。
日本語化のためにintl
も入れます。
flutter_riverpod
table_calendar
intl
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^3.0.0
flutter_riverpod: ^2.5.1
table_calendar: ^3.1.2
intl: ^0.19.0
まずはカレンダーを表示させる
import 'package:flutter/material.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return Scaffold(
body: TableCalendar(
firstDay: DateTime.utc(2010, 10, 16),
lastDay: DateTime.utc(2030, 3, 14),
focusedDay: DateTime.now(),
),
);
}
}
とりあえずこれだけでカレンダー自体は表示されます。簡単ですね!
firstDay
とlastDay
とfocusedDay
が必須プロパティなので、それだけあれば一旦OKです。
Riverpodを使ってタップした日を選択する
さて、ここからが本題です。
実現したい動作は「選択した日付を取得しアクティブにする」です。
そのために、選択した日付をRiverpodで状態管理します。
Providerを作る
import 'package:flutter_riverpod/flutter_riverpod.dart';
final selectedDateProvider = StateProvider<DateTime>((ref) => DateTime.now());
final focusedDateProvider = StateProvider<DateTime>((ref) => DateTime.now());
外部から変更可能なStateProvider
を作ります。
カレンダーにはイベント関数OnDaySelected
があり、それで2つのDateTime
(selectedDayとfocusedDay)を受け取ります。
TableCalendarにonDaySelectedを追加
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedDate = ref.watch(selectedDateProvider);
final focusedDate = ref.watch(focusedDateProvider);
return Scaffold(
body: TableCalendar(
firstDay: DateTime.utc(2010, 10, 16),
lastDay: DateTime.utc(2030, 3, 14),
focusedDay: focusedDate,
currentDay: DateTime.now(),
selectedDayPredicate: (day) {
return isSameDay(selectedDate, day);
},
onDaySelected: (selectedDay, focusedDay) {
print(selectedDay); // 選択した日がコンソールで表示
ref.read(selectedDateProvider.notifier).state = selectedDay; // 選択した値に書き換え
ref.read(focusedDateProvider.notifier).state = focusedDay; // 選択した値に書き換え
},
),
);
}
}
これで日付を選択できるようになりました!
カレンダーのデザインを変更
最後にカレンダーのデザインを少し変更します。
色を管理するThemeData
の作成と、カレンダーを日本語化します。
ThemeDataを作成
import 'package:flutter/material.dart';
class AppTheme {
static const Color lightGray = Color(0xFFF0F0F0);
static const Color darkGray = Color(0xFFCCCCCC);
static final ThemeData lightTheme = ThemeData(
colorScheme: ColorScheme.fromSeed(
brightness: Brightness.light,
seedColor: const Color(0xFFF5BFC0),
primary: const Color(0xFFF5BFC0),
),
useMaterial3: true,
);
}
main.dartでThemeDataを設定 & 日本語化
私はgo_routerを使ってるので少しデフォルト違いますが、大体こんな感じです。
import 'package:flutter/material.dart';
import 'package:h_calender/router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:h_calender/theme.dart';
import 'package:intl/date_symbol_data_local.dart';
void main() {
MyApp.run();
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'カレンダー',
routerConfig: router,
theme: AppTheme.lightTheme, // AppThemeクラスを使用
);
}
static Future<void> run() async {
await initializeDateFormatting('ja_JP').then((_) {
WidgetsFlutterBinding.ensureInitialized();
runApp(const ProviderScope(
child: MyApp(),
));
});
}
}
最終的なカレンダーのコードはこちら!
import 'package:flutter/material.dart';
import 'package:h_calender/models/calendar.dart';
import 'package:h_calender/theme.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class HomePage extends ConsumerWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedDate = ref.watch(selectedDateProvider);
final focusedDate = ref.watch(focusedDateProvider);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
body: TableCalendar(
firstDay: DateTime.utc(2010, 10, 16),
lastDay: DateTime.utc(2030, 3, 14),
focusedDay: focusedDate,
currentDay: DateTime.now(),
selectedDayPredicate: (day) {
return isSameDay(selectedDate, day);
},
locale: 'ja_JP',
headerStyle: const HeaderStyle(
formatButtonVisible: false,
titleCentered: true,
),
calendarStyle: CalendarStyle(
todayDecoration: const BoxDecoration(
color: AppTheme.darkGray, // 今日の日付の背景色
shape: BoxShape.circle, // 形を丸く
),
selectedDecoration: BoxDecoration(
shape: BoxShape.circle, color: colorScheme.primary // 選択した日付の背景色
),
todayTextStyle: const TextStyle(
color: Colors.white, // 今日の日付の文字色
),
selectedTextStyle: const TextStyle(
color: Colors.white, // 選択した日付の文字色
),
),
onDaySelected: (selectedDay, focusedDay) {
ref.read(selectedDateProvider.notifier).state = selectedDay;
ref.read(focusedDateProvider.notifier).state = focusedDay;
},
),
);
}
}
カレンダーのデザイン部分は肥大化するので別クラスで切り分けると良さそうですね!