Chapter 04

4.まずは見た目から(todo編)

antman
antman
2021.11.29に更新

todo_view.dartに記述する

todo_view.dartに下記の通りに記述してください。

todo_view.dart
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_datetime_picker/flutter_datetime_picker.dart';


class Todo extends HookConsumerWidget {
  const Todo({Key? key}) : super(key: key);
  
  Widget build(BuildContext context, WidgetRef ref) {
    List<Widget> tiles = [];
    return Scaffold(
      body: ListView(children: tiles),
      //ListViewでtilesを表示します。
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: () async {
          await showModalBottomSheet<void>(
            context: context,
            useRootNavigator: true,
            isScrollControlled: true,
            builder: (context2) {
              return HookConsumer(
	        //contextがshowModalBottomSheetのものにかわるので
		//HookConsumerWidgetで使えていたHookが使えなくなります。
		//なのでHookConsumerを使ってhookを使えるようにします。
                builder: (context3, ref, _) {
                  final limit = useState<DateTime?>(null);
                  //DatePickerで取得した値を保存するために使用
                  return Padding(
                    padding: MediaQuery.of(context3).viewInsets,
                    child: Column(
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[
                        TextField(
                          decoration: InputDecoration(
                            labelText: 'タスク名',
                          ),
                          onChanged: (value) {
                            //入力中の文字列を保存するために使います
                          },
                          onSubmitted: (value) {
			    //入力中の文字列を保存するために使います
                          },
                        ),
                        TextField(
                          decoration: InputDecoration(
                            labelText: '説明',
                          ),
                          onChanged: (value) {
                            //同様
                          },
                          onSubmitted: (value) {
                          },
                        ),
                        Table(
                          defaultVerticalAlignment:
                              TableCellVerticalAlignment.values[0],
                          children: [
                            TableRow(
                              children: [
                                ElevatedButton(
                                  onPressed: () {
                                    DatePicker.showDateTimePicker(
                                      context,
                                      showTitleActions: true,
                                      minTime: DateTime.now(),
                                      onConfirm: (date) {
                                        //期限を保存するために使います
					limit.value = date;
                                      },
                                      currentTime: DateTime.now(),
                                      locale: LocaleType.jp,
                                    );
                                  },
                                  child: Row(
                                    children: [
                                      Icon(Icons.calendar_today),
                                      Text(limit.value == null
                                          ? ""
                                          : limit.value
                                              .toString()
                                              .substring(0, 10)),
                                    ],
                                  ),
                                ),
                                Container(
                                  width: 10,
                                ),
                                ElevatedButton(
				  //決定ボタンです
                                  onPressed: () {
                                    Navigator.pop(context3);
                                  },
                                  child: Text('OK'),
                                ),
                              ],
                            ),
                          ],
                        ),
                      ],
                    ),
                  );
                },
              );
            },
          );
          //ここでfloatingActionButtonの処理は終わりです。
        },
      ),
    );
  }
}

長いですね、正直これを書くのは苦痛でした。
長いには長いですがそのほとんどはuiを記述しているだけなので特段難しい内容ではありませんしほとんどの行数は()や{}、コメントによって構成されています。
ここで読むのをやめないでくださいお願いします;;

root.dart
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:(アプリ名)/view/todo/todo_view.dart';
//todo_view.dartをインポート

class Root extends HookConsumerWidget {
  const Root({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    List<String> keys = ["Home", "TimeLine", "Settings"];
    List<Icon> icons = [
      Icon(Icons.home),
      Icon(Icons.timeline),
      Icon(Icons.settings),
    ];
    final page_state = useState(0);
    List<BottomNavigationBarItem> navi_items = [];
    for (int i = 0; i < keys.length; i++) {
      navi_items.add(BottomNavigationBarItem(
        icon: icons[i],
        label: keys[i],
      ));
    }
    return Scaffold(
      appBar: AppBar(
        title: Text(keys[page_state.value]),
      ),
      body: Todo(),//Todo画面を表示!
      bottomNavigationBar: BottomNavigationBar(
        items: navi_items,
        currentIndex: page_state.value,
        onTap: (index) {
          page_state.value = index;
        },
      ),
    );
  }
}

root.dartにtodo_view.dartをインポートし、body部分をTodo()に書き換えとりあえず実行してみましょう。
どうでしょうか? flutterのチュートリアルと同じく、floatingActionButtonが表示されており、それをタップすると様々な値を入力するためのウィジェットが表示されるのがわかるともいます。
body部分は単純にからのリストをListViewで表示しているだけなので、floatingActionButtonで何が行われているのかを説明していきます。

floatingActionButtonでは何が起きているの?

floatingActionButtonのonPressedをみてみるとshowModalBottomSheetと記述されているのがわかると思います。

onPressed: () async {
          await showModalBottomSheet<void>(
	             context: context,
                     useRootNavigator: true,
                     isScrollControlled: true,
                     builder: (context2) {

showModalBottomSheetは下からでてくるシートを表示してくれます。そしてbuilderにはシート内に表示するウィジェットを記述します。ここで注目してほしいのは、builderの部分でcontext2というあたらしいcontextがつくられているのがわかると思います。これによりHookConsumerWidgetのcontextが使えなくなり、HookやProviderが使えなくなってしまいます。
そこで、builderないでもう一度HookやProviderを使えるようにするためにHookConsumerを使います。

builder: (context2) {
              return HookConsumer(
	        //contextがshowModalBottomSheetのものにかわるので
		//HookConsumerWidgetで使えていたHookが使えなくなります。
		//なのでHookConsumerを使ってhookを使えるようにします。
                builder: (context3, ref, _) {

HookConsumerのbuilderにより、context3というあたらしいcontextがつくられ、HookやProviderが使えるようになります。
さて、残りは簡単なウィジェットが残るのみとなりましたのでざっくりと解説していきます。

残りをざっくりと解説

  • Padding
Padding(
                    padding: MediaQuery.of(context3).viewInsets,

余白を指定するウィジェットです。今回は文字列を入力する際に出現するソフトウェアキーボードに、bottomsheetが被らせないためにしようしています。

  • Column
Column(
                      mainAxisSize: MainAxisSize.min,
                      children: <Widget>[

複数のウィジェットを縦に並べてくれます。

  • TextField
TextField(
                          decoration: InputDecoration(
                            labelText: 'タスク名',
                          ),
                          onChanged: (value) {
                            //入力中の文字列を保存するために使います
                          },
                          onSubmitted: (value) {
			    //入力中の文字列を保存するために使います
                          },
                        ),

decorationをつけることによって薄くラベルテキストをTextFieldないに表示することができます。
onChangedでは入力中の値が無名関数の引数、valueに代入され1文字入力されるごとに無名関数が実行されます。
onSbmittedはonChangedとほとんど同じですが、入力が決定されたときに実行されます。

  • Table
Table(
                          defaultVerticalAlignment:
                              TableCellVerticalAlignment.values[0],
                          children: [

ウィジェットをテーブル状に配置してくれます。今回はボタン2つを横に並べるためにしようしています。Rowでも良いのですがTableをつかうと配置を勝手にいい感じにしてくれるので今回はこちらを使用しました。

  • ElevatedButton
ElevatedButton(
                                  onPressed: () {

ボタンです。

  • Row
 Row(
                                    children: [

ウィジェットを横に並べてくれます。

  • DatePicker
DatePicker.showDateTimePicker(
                                      context,
                                      showTitleActions: true,
                                      minTime: DateTime.now(),
                                      onConfirm: (date) {
                                        //期限を保存するために使います
					limit.value = date;
                                      },
                                      currentTime: DateTime.now(),
                                      locale: LocaleType.jp,
                                    );

日時を入力するためのウィジェットを表示してくれます。flutter_datetime_pickerパッケージを使っています。onConfirmに値が決定された際の処理を記述します。今回はlimit.valueに決定された値を入力して画面を再描画しています。
(参照:https://pub.dev/packages/flutter_datetime_picker)

  • 3項演算子
Text(limit.value == null
                                          ? ""
                                          : limit.value
                                              .toString()
                                              .substring(0, 10)),

これはlimit.valueがnullならば""を表示。それ以外ならばlimit.valueを10文字目まで表示します。

まとめ

今回はtodo情報を入力するための画面であるtodo_view.dartを作りました。今後、driftやstate_notifierを使って値を保存できるようにし、onChangedやonSubmittedで保存を行う処理を記述していくことになります。次の章では、timelineとsettingsを一気に作ります。画面づくりはあともう少しで終わりです!