Todo クラスの作成
まずは TODO のクラスを作成します。
class MyHomePage extends StatefulWidget {
// 略
}
class _MyHomePageState extends State<MyHomePage> {
// 略
}
+ class Todo {
+ String title;
+ IconData icon;
+
+ Todo(this.title, this.icon);
+ }
Todo(this.title, this.icon);
はコンストラクタです。
Flutter ではアイコンは IconData
クラスとして持ち、Icon(IconData icon)
というふうに UI で表示できます。マテリアルデザインのアイコンは Icons
クラスで定義されています。
_todoItems の変更
_todoItems
を文字列のリストから、先ほど作成した Todo
のリストに変更します。
class _MyHomePageState extends State<MyHomePage> {
- List<String> _todoItems = [
- "英語の課題",
- "牛乳を買う",
- ];
+ List<Todo> _todoItems = [
+ Todo("英語の課題", Icons.description),
+ Todo("牛乳を買う", Icons.local_grocery_store),
+ ];
それに伴って、いくつかの箇所を修正していきます。
class _MyHomePageState extends State<MyHomePage> {
List<Todo> _todoItems = [
Todo("英語の課題", Icons.description),
Todo("牛乳を買う", Icons.local_grocery_store),
];
- void _addTodo(String title) {
+ void _addTodo(Todo todo) {
setState(() {
- _todoItems.add(title);
+ _todoItems.add(todo);
});
}
body: ListView.builder(
itemCount: _todoItems.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
- title: Text(_todoItems[index]),
+ title: Text(_todoItems[index].title),
trailing: IconButton(
icon: const Icon(Icons.more_vert),
onPressed: () => showDialog(
context: context,
builder: (BuildContext context) => AlertDialog(
- title: Text(_todoItems[index]),
+ title: Text(_todoItems[index].title),
actions: [
// 略
],
),
),
),
),
);
},
),
Todo を追加する部分では、とりあえず Icons.add
(FloatingActionButton のアイコンと同じ)を追加するようにします。
floatingActionButton: FloatingActionButton(
onPressed: () async {
final String? title = await Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => CreatePage()));
- if (title != null && title != "") _addTodo(title);
+ if (title != null && title != "") _addTodo(Todo(title, Icons.add));
},
child: const Icon(Icons.add),
),
これでひとまず _todoItems
を変更する前の状態と同じになりました。
次はここにアイコンを表示させてみましょう。
アイコンを表示する
ListView を変更してアイコンを表示しましょう。
ListTile の leading を指定します。
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
+ leading: Icon(
+ _todoItems[index].icon,
+ size: 35.0,
+ ),
title: Text(_todoItems[index].title),
trailing: IconButton(
// 略
),
),
);
}
アイコンが表示できました。
ですが、まだ追加する TODO のアイコンは指定できないので、次は TODO を入力するときにアイコンも選べるようにしていきましょう。
flutter_iconpicker パッケージをインストールする
アイコンを選ぶのに、今回は flutter_iconpicker という外部パッケージを利用します。
まずは pubspec.yaml
で以下のように追加します。
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.2
flutter_iconpicker: ^3.0.4 # 追加
そしてパッケージをインストールします。Android Studio なら「pub get」というところをクリックすればインストールが開始されます。
また、以下のようなコマンドでもインストールできます。
$ flutter pub get
flutter_iconpicker を使う
インストールした flutter_iconpicker を create_page で使ってみましょう。
外部パッケージを使うときはインポートする必要があります。
import 'package:flutter/material.dart';
+ import 'package:flutter_iconpicker/flutter_iconpicker.dart';
_pickIcon()
では、FlutterIconPicker.showIconPicker(context)
でアイコンを選択するダイアログを表示し、選択したアイコンのデータを _icon
に入れています。
class _CreatePageState extends State<CreatePage> {
String _title = "";
+ IconData? _icon;
+ void _pickIcon() async {
+ IconData icon = await FlutterIconPicker.showIconPicker(context);
+ setState(() {
+ _icon = icon;
+ });
+ }
flutter_iconpicker ダイアログを表示して選択したアイコンを表示できるように、ボタンとアイコンを追加します。
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text("TODOを入力してください"),
TextField(
onChanged: (String text) => _title = text,
),
+ Icon(
+ _icon,
+ size: 45.0,
+ ),
+ ElevatedButton(
+ child: const Text("Pick Icon"),
+ onPressed: () => _pickIcon(),
+ ),
ElevatedButton(
child: const Text("Add"),
onPressed: () => Navigator.pop(context, _title),
),
],
),
),
ボタンを押すと、ダイアログが表示されます。
そしてアイコンを選択できます。
my_home_page に選択したアイコンを渡す
テキストと一緒にアイコンも my_Home_page に渡すように変更します。先ほど作成した Todo
クラスを用いましょう。
my_home_page にある Todo
クラスを使うので、インポートを忘れずに。
import 'package:flutter/material.dart';
import 'package:flutter_iconpicker/flutter_iconpicker.dart';
+ import 'my_home_page.dart';
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 略
ElevatedButton(
child: const Text("Add"),
- onPressed: () => Navigator.pop(context, _title),
+ onPressed: () => Navigator.pop(context, Todo(_title, _icon)),
),
],
),
),
my_home_page のほうも、受け取った Todo
をリストに追加するように修正します。
floatingActionButton: FloatingActionButton(
onPressed: () async {
- final String? title = await Navigator.of(context)
- .push(MaterialPageRoute(builder: (context) => CreatePage()));
+ final Todo? todo = await Navigator.of(context)
+ .push(MaterialPageRoute(builder: (context) => CreatePage()));
- if (title != null && title != "") _addTodo(Todo(title, Icons.add));
+ if (todo != null) _addTodo(todo);
},
child: Icon(Icons.add),
),
これで選択したアイコンを TODO リストへ追加できるようになりました。
細かい修正
まずは create_page の Widget ツリー全体を Container ウィジェットで覆い、外側の padding を追加します。
- body: Center(
+ body: Container(
+ padding: const EdgeInsets.all(40.0),
+ child: Center(
// 略
),
+ ),
create_page の UI を以下のように修正します。
- TextField の上のテキストを消して、TextField にラベルテキストを追加
- アイコンと Pick Icon ボタンを
Row
Widget を使って横並びに - アイコンが選択されていないときに none という文字を表示
body: Container(
padding: const EdgeInsets.all(40.0),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const Text("TODOを入力してください"),
TextField(
+ decoration: const InputDecoration(
+ labelText: "TODO title",
+ ),
onChanged: (String text) => _title = text,
),
- Icon(
- _icon,
- size: 45.0,
- )
- ElevatedButton(
- child: const Text("Pick Icon"),
- onPressed: () => _pickIcon(),
- ),
+ Container(
+ padding: const EdgeInsets.only(top: 20.0, bottom: 30.0),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ _icon != null
+ ? Icon(
+ _icon,
+ size: 45.0,
+ )
+ : const Text("none"),
+ ElevatedButton(
+ child: const Text("Pick Icon"),
+ onPressed: () => _pickIcon(),
+ ),
+ ],
+ ),
+ ),
ElevatedButton(
child: const Text("Add"),
onPressed: () => Navigator.pop(context, Todo(_title, _icon)),
),
],
),
),
),
あと、Add ボタンが押されたときに _title
か _icon
のどちらかが入力されていないとメッセージが出るようにしておきます。
class _CreatePageState extends State<CreatePage> {
String _title = "";
IconData? _icon;
+ bool _isError = false;
body: Container(
padding: const EdgeInsets.all(40.0),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 略
ElevatedButton(
child: ElevatedButton(
child: const Text("Add"),
- onPressed: () => Navigator.pop(context, Todo(_title, _icon)),
+ onPressed: () {
+ if (_title == "" || _icon == null) {
+ setState(() {
+ _isError = true;
+ });
+ return;
+ }
+ Navigator.pop(context, Todo(_title, _icon));
+ },
),
),
+ if (_isError)
+ const Text(
+ "全ての項目を埋めてください",
+ style: TextStyle(color: Colors.red),
+ ),
],
),
),
このようになります。
これで完成です!
Flutter で TODO アプリを作ることができました!