ソースコードから理解する技術-UnderSourceCode

手を動かす(プログラムを組む)ことで技術を理解するブログ

はじめてのFlutter - Drawer、List、State を使ってみる

タイトルにもあるようにFlutterを触り始めました。

Install - Flutter
こちらに「Get Started」のインストールから始め、公式サイトのチュートリアルをいくつかやってみた後で、理解を深めるために以下のようなサンプルをつくってみました。

右下の「+」を押すとカウントが増える
f:id:UnderSourceCode:20210124121434p:plain

上の画面の左上をタップすると Drawer が開く
f:id:UnderSourceCode:20210124122248p:plain

「Item List」をタップするとリストが表示され、「+」を押した数だけ「Item N」が増える
f:id:UnderSourceCode:20210124122355p:plain

これらはFlutterのプロジェクトを「New Application Project」から作成した雛形と、以下の「Add Drawer」のチュートリアルにあるソースを合体させたものに手を加えてつくりました。
Add a Drawer to a screen - Flutter

タイトルにあるようにFlutterのDrawer、List、Stateを使っています。以下にソースを書いておきます。

私自身は公式のチュートリアルをやった後に以下を実装してみました。公式チュートリアルをやったあと、以下の例など色んなサンプルを動かしてみることで理解が早まる気がします。

ソースとDrawer、List、Stateについて

ソースは分かりやすいよう「lib/main.dart」に纏めてみました。ローカルで試す場合もプロジェクトを作成して以下のソースを張り付ければ動くかと思います(バージョン変更などなければですが・・・)

lib/main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Drawer List Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  List<String> _listItems = new List<String>();

  void _incrementCounter() {
    setState(() {
      _counter++;
      _listItems.add('Item $_counter');
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      // add drawer
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: _createDrawerList(),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }

  List<Widget> _createDrawerList() {
    return <Widget>[
      DrawerHeader(
        child: Text('Drawer Header',
            style: TextStyle(color: Colors.white, fontSize: 20)),
        decoration: BoxDecoration(
          color: Colors.blue,
        ),
      ),
      ListTile(
        leading: Icon(Icons.home),
        title: Text('Home'),
        onTap: () {
          Navigator.pop(context);
        },
      ),
      ListTile(
        leading: Icon(Icons.list),
        title: Text('Item List'),
        onTap: () {
          Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => ListScreen(listItems: _listItems),
            ),
          );
        },
      ),
    ];
  }
}

class DetailScreen extends StatefulWidget {
  final DetailRecord detailRecord;

  DetailScreen({Key key, @required this.detailRecord}) : super(key: key);

  @override
  _DetailScreenState createState() => _DetailScreenState();
}

class _DetailScreenState extends State<DetailScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.detailRecord.title),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Text(widget.detailRecord.text),
      ),
    );
  }
}

class DetailRecord {
  String title;
  String text;
  DetailRecord(String title, String text) {
    this.title = title;
    this.text = text;
  }
}

class ListScreen extends StatefulWidget {
  final List<String> listItems;

  ListScreen({Key key, @required this.listItems}) : super(key: key);

  @override
  _ListScreenState createState() => _ListScreenState();
}

class _ListScreenState extends State<ListScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Item List'),
      ),
      body: ListView.builder(
        itemBuilder: (BuildContext context, int index) {
          return Container(
              decoration: BoxDecoration(
                border: Border(
                  bottom: BorderSide(color: Colors.black38),
                ),
              ),
              child: ListTile(
                leading: const Icon(Icons.info),
                title: Text(widget.listItems[index]),
                onTap: () {
                  DetailRecord detailRecord =
                      new DetailRecord("Item Detail", widget.listItems[index]);
                  Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) =>
                          DetailScreen(detailRecord: detailRecord),
                    ),
                  );
                },
              ));
        },
        itemCount: widget.listItems.length,
      ),
    );
  }
}
Drawer

メインとなる最初の画面を作る「_MyHomePageState」クラスの「build」メソッドにて、文字通り「drawer」について記述している箇所があります。その中で「_createDrawerList」というWidgetのListを作成するメソッドを呼び出しています。

この部分がDrawerを作っている所となります。物凄い大雑把な説明になってしまうのですが、要は複数のWidgetをListで集める形でDrawerを作っているような感じです。

List

「+」をタップした数の「Item N」を表示するリストは、「ListScreen」クラスとその状態を保持する「_ListScreenState」クラスで構成しています。リストに表示する複数の値は「listItems」というString型のListとして保持しています。この「listItems」をリストを表示する「ListView.builder」で使うことでリスト形式での表示を行っています。

「listItems」は「+」をタップするごとに数が増えるのですが、それについては次の「State」で触れます。ちなみにリストで表示される「Item N」をタップすると簡単な詳細画面に遷移するようにもしてあります。ソースを動かせる方はやってみてください。

State

State = 状態ですが、今回のように「+」をタップする度に動的に増える値などはStateとして保持します。「+」ボタンはメインとなる最初の画面の「_MyHomePageState」クラスの「build」メソッドにて「floatingActionButton」の箇所にて定義しています。

この「_MyHomePageState」クラスですが、Stateを保持するよう「class _MyHomePageState extends State」のように「State」を継承して作られています。

「+」をタップした数とリストに表示する値は、Staetを継承した「_MyHomePageState」クラス内のメンバ変数「_counter」「_listItems」で保持します。「+」をタップした時に呼ばれる「_incrementCounter」メソッドを見ると「setState」内で「_counter」「_listItems」がそれぞれ増えている or 追加していることが分かるかと思います。

先にリストを表示する「ListScreen」クラスを説明したところで「listItems」というString型のListを表示していると書きましたが、「builder: (context) => ListScreen(listItems: _listItems)」の箇所でStateとして保持していた「_listItems」を「ListScreen」クラスに渡しています。