🦷
Stepper classを使用して予約フローを作成する
👤対象者
- 予約画面のフローを作ってみたい
- Stepper classの活用例
記事の内容
歯医者さんの予約システムをFlutterで再現するのを今回はやってみようと思います。こちらがデモアプリの動画です。
デンタルクリニック予約アプリ
アプリケーションの概要
このFlutterアプリケーションは、歯科医院の予約システムを提供する簡単なアプリです。
主な機能
-
ホーム画面
- クリニック名(俺ちゃんデンタルクリニック)の表示
- 予約ページへの遷移ボタン
- Material Design 3に基づいたモダンなUI
-
予約画面
- ステップ形式の予約フォーム
- 直感的な操作性
技術的な実装について
- 状態管理はsetState()を使用
- checkboxの状態管理を行なう
使用している主な機能
-
MaterialApp
: アプリケーションのベース設定- デバッグバナーの非表示化
- カスタムテーマカラーの設定
- Material Design 3の採用
-
Navigator
: 画面遷移の実装-
MaterialPageRoute
を使用したページ遷移 -
StatefulWidget
による状態管理
-
example
サンプルコード
main.dart
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'デンタルクリニック予約',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF40E0D0), // ターコイズブルー
),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('俺ちゃんデンタルクリニック'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
child: ElevatedButton(onPressed: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) => const DentalReservationPage()));
},
child: const Text('予約する')),
)
);
}
}
class DentalReservationPage extends StatefulWidget {
const DentalReservationPage({super.key});
State<DentalReservationPage> createState() => _DentalReservationPageState();
}
class _DentalReservationPageState extends State<DentalReservationPage> {
int _currentStep = 0;
final Map<String, bool> _symptoms = {
'歯がしみる': false,
'歯が痛い': false,
'歯茎が痛い': false,
'歯が痺れる': false,
'歯が折れた': false,
'悪いところがないか検査してほしい': false,
};
List<Step> getSteps() {
return [
Step(
title: const Text('症状の選択'),
content: Column(
children: _symptoms.entries.map((entry) {
return CheckboxListTile(
title: Text(entry.key),
value: entry.value,
onChanged: (bool? value) {
setState(() {
_symptoms[entry.key] = value ?? false;
});
},
);
}).toList(),
),
isActive: _currentStep >= 0,
),
Step(
title: const Text('予約内容の確認'),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('選択された症状:', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
..._symptoms.entries
.where((entry) => entry.value)
.map((entry) => Padding(
padding: const EdgeInsets.only(left: 16, bottom: 4),
child: Text('・${entry.key}'),
))
.toList(),
],
),
isActive: _currentStep >= 1,
),
Step(
title: const Text('完了'),
content: const Column(
children: [
Icon(Icons.check_circle_outline, size: 48, color: Color(0xFF40E0D0)),
SizedBox(height: 16),
Text('予約申し込みが完了しました。\nご来院をお待ちしております。',
textAlign: TextAlign.center),
],
),
isActive: _currentStep >= 2,
),
];
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('予約フォーム'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Stepper(
type: StepperType.horizontal,
currentStep: _currentStep,
onStepContinue: () {
if (_currentStep < getSteps().length - 1) {
setState(() {
_currentStep++;
});
}
},
onStepCancel: () {
if (_currentStep > 0) {
setState(() {
_currentStep--;
});
}
},
steps: getSteps(),
controlsBuilder: (BuildContext context, ControlsDetails details) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
children: [
if (_currentStep < getSteps().length - 1)
ElevatedButton(
onPressed: details.onStepContinue,
child: const Text('次へ'),
),
if (_currentStep > 0) ...[
const SizedBox(width: 12),
TextButton(
onPressed: details.onStepCancel,
child: const Text('戻る'),
),
],
],
),
);
},
),
);
}
}
参考リソース
Flutterの開発に関する詳細な情報は以下をご覧ください:
Discussion