Re:birth エンジニアリング

Flultterとテックブログと時々iOS

Flutterで画面遷移に使うNavigatorについて学習する

本日は複数画面の切り替え機能である「ナビゲーション機能」について学習します。 ナビゲーション機能はNavigatorというクラスが用意されているようです。 iOSでいうところのUINavigationControllerみたいなものだと推測しています。

Navigator クラスについて

端的に説明するとNavigatorクラスは次の2つのメソッドを覚えればいいそうです。

画面切替先にプッシュする時

Navigator.push([BuildContext], [Route]);

前の画面に戻る時

Navigator.pop([BuildContext]);

サンプルコードについて

では早速サンプルコードを書いていきます。

main.dart

import 'package:flutter/material.dart';
import 'package:practice_app/first_screen.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final title = 'Navigatorのサンプルコード';
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: true,
      title: 'Navigator App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: const Color(0xff2196f3),
        accentColor: const Color(0xff2196f3),
        canvasColor: const Color(0xfffafafa),
      ),
      home: FirstScreen(),
    );
  }
}

first_screen.dart

import 'package:flutter/material.dart';
import 'package:practice_app/second_screen.dart';

class FirstScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First'),
      ),
      body: Center(
        child: Text('First View'),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: 1,
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            title: Text('First'),
            icon: Icon(Icons.home),
          ),
          BottomNavigationBarItem(
            title: Text('Second'),
            icon: Icon(Icons.navigate_next),
          ),
        ],
        onTap: (int value) {
          if (value == 0) {
            /// First Icon が押されたときは何も起きない
          } else if (value == 1) {
            /// Second Icon が押されたときは画面先にプッシュする
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) {
                  return SecondScreen();
                }
            )
            );
          }
        },
      ),
    );
  }
}

second_screen.dart

import 'package:flutter/material.dart';

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Next Screen"),
      ),
      body: Center(
        child: Text('Next'),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: 0,
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            title: Text('First'),
            icon: Icon(Icons.navigate_before)
          ),
          BottomNavigationBarItem(
            title: Text('Second'),
            icon: Icon(Icons.android)
          ),
        ],
        onTap: (int value) {
          if (value == 0) {
            /// First Icon が押されたときは前の画面に戻る
            Navigator.of(context).pop();
          }
        },
      ),
    );
  }
}

今回はmain.dart, first_screen.dart, second_screen.dartの3つのクラスを使っています。 画面遷移の方法としてBottomNavigationBarを作成してiOSでいうところのUITabbarControllerを使ってTabbarを設置しました。

f:id:qed805:20200130074846p:plain
プロジェクトファイルの構成

これでアプリをビルドすると下の方は画面が表示されます。

f:id:qed805:20200130075058p:plain
First Screenの画面

f:id:qed805:20200130075121p:plain
Second Screen の画面

下のタブバーのボタンをタップするとそれぞれの画面が表示されます。

MaterialPageRoute について

MaterialPageRoute というのは PageRouteのサブクラスです。 さらにPageRouteというのはRouteのサブクラスです。 つまり、Routeのサブクラスです。 PageRouteクラスは、現在の画面情報だけでなくプッシュ先の画面情報も保存しているクラスになります。 簡単に言えば、画面情報や移動先の情報を保存している感じになります。 MaterialPageRouteの呼び出しにはbuilderという引数があります。 builderWidgetBuilderという関数シグネーチャーを引数に指定します。 今回はSecondScreenにプッシュさせますのでこちらを指定しています。 これでSecondScreen に画面遷移させることができます。

今回は画面遷移について学びましたが、Flutter では「ルーティング」の概念に位置づけれられいるそうです。 下記のリンクは日本語でのFlutter の画面遷移の解説がされています。 これを見ればWebでいうところの「ルーティング」と一緒ということが分かります。

flutter.ctrnost.com

ということで今回は以上になります。

FlutterのTabbarであるBottomNavigationBarについて学習する

今回はiOSでいうところのUITabbarControllerに当たるBottomNavigationBarについて学習します。

BottomNavigationBar とは

AppBarのように画面の上側ではなく、下側にもバーを表示させたいことがあります。 この下側にバーを設置するウィジェットがFlutter ではBottomNavigationBarが用意されています。

BottomNavigationBarの基本は以下の通りです。

BottomNavigationBar(
        currentIndex: [int値],
        items: <BottomNavigationBarItem>[BottomNavigationBarItemのリスト],
        onTap: [タップイベント],
      )

それぞれのプロパティの解説は次のようになります。

プロパティ 役割
currentIndex 現在、選択されているindex。このindexのアイコンが選択状態になる
items 表示するitem。BottomNavigationBarItemのリストが入る
onTap アイコンをタップしたときの処理。

アイコンをタップしたときの処理はonTapに入る関数内で書きます。 この関数にはタップした項目のindexを示す引数が必要で以下のように定義すれば大丈夫です。

void メソッド名(int value) {
// 処理内容
}

ただタップした項目を選択状態にするためには、この引数のvalueBottomNavigationBarの引数currentIndexの値が同じにする必要があります。そのため、変数を用意してメソッドの中で変数を更新させcurrentIndexにも同じ変数を設定する必要があります。

サンプルコードについて

それではBottomNavigationBarに関するサンプルコードを書いていきます。

tab_navigation_bar.dart

import 'package:flutter/material.dart';

class TabNavigationBar extends StatefulWidget {
  @override
  _TabNavigationBarState createState() => _TabNavigationBarState();
}

class _TabNavigationBarState extends State<TabNavigationBar> {
  String _message;
  var items = ['first view','second view'];
  int _index = 0;

  @override
  void initState() {
    _message = items.first;
    super.initState();
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('TabNavigationBar App'),
      ),
      body: Center(
        child: Text(
          _message, style: TextStyle(fontSize: 20.0),
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _index,
        items: <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            title: Text('first'),
            icon: Icon(Icons.favorite)
          ),
          BottomNavigationBarItem(
            title: Text('second'),
            icon: Icon(Icons.favorite),
          )
        ],
        onTap: _tapBottomIcon,
      ),
    );
  }

  void _tapBottomIcon(int index) {
    setState(() {
      _index = index;
      _message = items[_index] + 'を選択しました';
    });
  }
}

main.dart

import 'package:flutter/material.dart';
import 'package:practice_app/tab_navigation_bar.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final title = 'BottomNavigationBarのサンプルコード';
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: true,
      title: 'BottomNavigationBar App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: const Color(0xff2196f3),
        accentColor: const Color(0xff2196f3),
        canvasColor: const Color(0xfffafafa),
      ),
      home: TabNavigationBar(),
    );
  }
}

このサンプルコードを実行すると次のような画面が表示されます。

f:id:qed805:20200201134703p:plain
初期表示の画面

f:id:qed805:20200201134723p:plain
first アイコンをタップしたときの画面

f:id:qed805:20200201134741p:plain
second アイコンをタップしたときの画面

実際に触ってみると分かりますが、下側にUITabbarController みたいなタブバーが表示されて、アイコンをタップすると画面が切り替わるのが分かります。

本日はBottomNavigationBarについて学習しました。 ただこれだけではアイコンをタップしたことで文字を変更できるだけで画面遷移しているわけではありません。 実際にこれを使って画面遷移するためには次回に学ぶNavigatorウィジェットやFlutter のルーティングについて学ぶ必要があるそうです。

FlutterのScrollViewについて学習する

今回はFlutterのScrollViewことSingleChildScrollViewについて学習します。 SingleChildScrollViewは特別な設定をしなくてもウィジェットを中に入れておくだけで、そのサイズに応じて自動的にスクロール表示ができるウィジェットです。Scroll用のウィジェットになります。 これを使えば前回のような下側のバナーエラーが表示されることがなくなります。

SingleChildScrollViewについて

SingleChildScrollViewのインスタンスの生成方法についてです。

       SingleChildScrollView(
          child: ウィジェット
       )

引数のchildの中にリストで表示させたいウィジェットを入れていきます。childに入れたウィジェットで画面内に収まらないサイズになると上下にスクロールできるようになります。 iOSのUITableViewみたいにdelegateも必要ないのがとても新鮮です。

では、サンプルコードを書いていきます。

listview_sample.dart

import 'package:flutter/material.dart';

class SampleListView extends StatefulWidget {
  @override
  _SampleListViewState createState() => _SampleListViewState();
}

class _SampleListViewState extends State<SampleListView> {
  String _message;
  int _index;

  @override
  void initState() {
    _message = 'OK';
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    List<int> text = [
      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
    ];
    return Scaffold(
        appBar: AppBar(
          title: Text('Sample List View App'),
        ),
        body:
        SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            mainAxisAlignment: MainAxisAlignment.spaceAround,
            children: <Widget>[
              Text(
                _message,
                style: TextStyle(fontSize: 20.0),
              ),
              for (var index in text)
                ListTile(
                  leading: Icon(Icons.favorite),
                  title: Text('$index'),
                  selected: _index == index,
                  onTap: () {
                    _index = index;
                    _tapTile();
                  },
                )
            ],
          ),
        ));
  }

  void _tapTile() {
    setState(() {
      _message = 'No. $_index を選択しました';
    });
  }
}

main.dart

import 'package:flutter/material.dart';
import './listview_sample.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final title = 'SingleChildScrollViewのサンプルコード';
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: true,
      title: 'SingleChildScrollView App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: const Color(0xff2196f3),
        accentColor: const Color(0xff2196f3),
        canvasColor: const Color(0xfffafafa),
      ),
      home: new SampleListView(),
    );
  }
}

これをビルドすると下記のように画面が表示されます。

f:id:qed805:20200127231808p:plain
SingleChildScrollViewのサンプル1

f:id:qed805:20200127231829p:plain
SingleChildScrollViewのサンプル2

実際にシミュレータなどで試して頂くと上下にスクロールできることが分かります。

SingleChildScrollView まで学習するとFlutterで有名なiOSアプリのレイアウトを真似して何かしらアプリを開発できるようになりそうですね。 SingleChildScrollViewにGridViewを乗せればインスタグラムみたいなアプリのレイアウトをサクッと作れそうです。

あとはiOSのTableViewみたいにheaderやfooterViewの作り方やカスタムなウィジェットをタップしたときの挙動を制御するためのやり方が分かれば動的な画面遷移も実装できるようになりそう。

あとFlutterで残っていそうなウィジェット

  • TabbarController
  • Drawer
  • SegmentController ?

辺りです。

これらも学習していく予定です。

FlutterでListViewをタップした時の挙動を実装する

前回はListViewの表示について学習しました。 実際のアプリ開発では画面の表示だけでなくそのUIをタップしたときの挙動も実装しなければならないことが多いです。 今回はそのListViewで表示されているものをタップしたときの処理を実装していきます。

ListViewの中にFlatButtonなどのButtonを追加してもいいですが、ListViewには専用のウィジェットListTileが用意されています。

ListTileについて

まずはListTileのインスタンスの生成の仕方について学びます。

            ListTile(
              leading: [項目の左側に表示するIcon],
              title: [表示させる文字],
              selected: [その項目の選択状態を管理するフラグ。trueであれば選択されている状態],
              onTap: () {
                // タップされたときのイベントを書きます
              },

これだけ覚えればListTileの基本はできたようなものです。 実際にサンプルコードを見ていきます。

listview_sample.dart

import 'package:flutter/material.dart';

class SampleListView extends StatefulWidget {
  @override
  _SampleListViewState createState() => _SampleListViewState();
}

class _SampleListViewState extends State<SampleListView> {
  String _message;
  int _index;

  @override
  void initState() {
    _message = 'OK';
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    List<int> text = [1,2,3,4,5,6,7,8,9,10];
    return Scaffold(
      appBar: AppBar(
        title: Text('Sample List View App'),
      ),
      body: Column(
        children: <Widget>[
          Text(
            _message,
            style: TextStyle(
              fontSize: 20.0
            ),
          ),
          ListView(
            shrinkWrap: true,
            padding: EdgeInsets.all(10.0),
            children: <Widget>[
              for (var index in text)
//                Text(i.toString(), style: TextStyle(
//                backgroundColor: Colors.green,
//                  fontSize: 30.0)),
            ListTile(
              leading: Icon(Icons.favorite),
              title: Text('$index'),
              selected: _index == index,
              onTap: () {
                _index = index;
                _tapTile();
              },
            )

            ],
          )
        ],
      ),
    );
  }
  void _tapTile() {
    setState(() {
      _message = 'No. $_index を選択しました';
    });
  }
}

main.dart

import 'package:flutter/material.dart';
import './listview_sample.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final title = 'ListViewのサンプルコード';
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: true,
      title: 'ListView App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: const Color(0xff2196f3),
        accentColor: const Color(0xff2196f3),
        canvasColor: const Color(0xfffafafa),
      ),
      home: new SampleListView(),
    );
  }
}

以上がサンプルのコードになります。 こちらをビルドすると次のスクショのように表示されます。

f:id:qed805:20200126231121p:plain
ListViewのListTileを使った画像

サンプルコードからはわかりにくいですが、 onTapで処理が走ったあとにselectedの値がtrueであれば選択状態を表しているみたいです。 今回はListViewの各項目にindexで数字の連番を張って、ウィジェット_indexの変数をもたせて_indexindexを比較して同じであれば項目が選択状態になるように実装しました。

ここまででListViewとListTileのそれぞれの実装方法について学習できました。 ただ、ListTileを実装してもその項目が画面上にすべて収まらないときにはエラーのバナーが見えてしまいますね。

これはListViewにはスクロール機能がないために起きる現象なので、次回はスクロールできるウィジェットについて学習しようと思います。

FlutterのListViewについて学習する

本日はFlutterのListViewについて学習します。

ListViewとは

少し前に複数ウィジェットを配置するための縦に並べるColumnと横に並べるRowについて学習しました。 ListViewはこのColumnに似ています。 どちらかというとAndroidの知識があるとAndroidにすでにListViewがありますので役割自体はそのListViewと同じです。 Columnはウィジェットを縦に並べるウィジェットですが、FlutterのListViewはリストを表示するためのウィジェットです。 これを使うことで複雑な構造のウィジェットを構築することが容易になります。

サンプルコードについて

今回はSampleListViewを作成してリストを表示させます。

listview_sample.dart

import 'package:flutter/material.dart';

class SampleListView extends StatefulWidget {
  @override
  _SampleListViewState createState() => _SampleListViewState();
}

class _SampleListViewState extends State<SampleListView> {
  String _message;

  @override
  void initState() {
    _message = 'OK';
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    List<int> text = [1,2,3,4,5,6,7,8,9,10];
    return Scaffold(
      appBar: AppBar(
        title: Text('Sample List View App'),
      ),
      body: Column(
        children: <Widget>[
          Text(
            _message,
            style: TextStyle(
              fontSize: 40.0
            ),
          ),
          ListView(
            shrinkWrap: true,
            padding: EdgeInsets.all(10.0),
            children: <Widget>[
              for (var i in text) Text(i.toString(), style: TextStyle(
                backgroundColor: Colors.green,
                  fontSize: 30.0)),

            ],
          )
        ],
      ),
    );
  }
}

main.dart

import 'package:flutter/material.dart';
import './listview_sample.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final title = 'ListViewのサンプルコード';
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      debugShowCheckedModeBanner: true,
      title: 'ListView App',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: const Color(0xff2196f3),
        accentColor: const Color(0xff2196f3),
        canvasColor: const Color(0xfffafafa),
      ),
      home: new SampleListView(),
    );
  }
}

これをビルドすると下記のような表示が見られます。

f:id:qed805:20200126181726p:plain
ListViewを使ったサンプル

今回はfor文を使ってTextを10回作成して表示させるようにしました。 ListViewにTextを10回乗せて数字を表示させています。

サンプルコードからListViewの基本形が次のようにわかります。

          ListView(
            shrinkWrap: true,
            padding: [余白],
            children: <Widget>[],

shrinkWrapは追加されたウィジェットの大きさを自動調整するかどうかのフラグです。 今回は大きすぎたら自動調整するようにtrueと設定しました。 あとはchildrenに必要なウィジェットをリストで渡してやればいいだけです。

ただListView自体はScroll機能がないのかScroll機能があるウィジェットは別にSingleChildScrollViewウィジェットとして存在しているみたいです。 ちなみにListViewで画面に収まらないぐらいウィジェットを渡すとエラーみたいなバナーが表示されます。

f:id:qed805:20200126181739p:plain
ListViewで画面に収まらないときの表示

こちらの現象については今後調査してみたいと思います。

本日はListViewについて学習しました。