2021/09/01

【flutter】画面遷移の実装について説明する回

@ 古曳 純基

Flutter

今回は、flutterを用いた画面遷移についてノート感覚でまとめていきたいと思います。
flutterでの画面遷移の詳細に入る前に、専門用語について説明します。

  • ルート:複数の画面のこと
  • 画面遷移:ルートを切り替える

以上の専門用語をおさえたところで、画面遷移をどう実装していくのかを説明していきたいと思います。

基本的な画面遷移

画面遷移には、Navigatorというクラスを使用します。
Navigatorはスタックの原理を用いて、ページウィジット(あるページを描画するウィジット)を管理するウィジットのことです。

それではどのようにして画面遷移をするのか説明していきたいと思います。

新しいルートに遷移する場合

Navigator.push()メソッドを使用します。
pushされると元画面の上に新しい画面を積んでいくようなイメージです。
図で表すと以下のようになります。

元からあったfirst pageウィジットの上に新たな画面ウィジットsecond pageをプッシュすることによって表示する画面を切り替えているのです。

ある程度pushメソッドについてご理解いただけたと思うので、実際にpushメソッドの実装を見てみましょう。

static Future <T> push<T extends Object>(BuildContext context, Route<T> route)


pushメソッドはstaticメソッドとなっているため、Navigatorクラスをインスタンス化しなくても使用可能なメソッドであることがわかります。
第一引数のcontextには、MyAppウィジットのbuldメソッドに渡しているcontextが渡っているのではないかと考えています。
buildメソッドの引数contextには、そのbuildメソッドを実行することで返ってくるウィジット群の親ウィジット(Element)が入っています。
contextについてはこちらの記事が参考になると思います。https://qiita.com/ko2ic/items/f7bf98b4a30049027470#build%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%AEbuildcontext

第二引数に入っているRouteが1画面を描画するためのウィジットです。大抵の場合、ここにはMaterialPageRouteクラスを渡すこととなります。

元のルートに遷移する場合

逆に元の画面に戻りたい場合どうしたら良いでしょうか?
答えはNavigator.pop()メソッドを使用します。このメソッドでは、上に積まれている画面ウィジットをNavigatorという箱から取り出していくイメージです。
図で表すと以下のようになります。

この図では、second pageを箱から取り出すことによってNavigatorという箱にはfirst pageのみが残っている状態となり、画面に描画されるのはfirst pageとなります。

画面遷移の実装を以下に示します。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext cotext) {
    return MaterialApp(
      title: "Flutter",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: FirstPage(), //アプリを立ち上げた際に表示される初期画面
    );
  }
}

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("初期画面")),
      body: Center(
        child: RaisedButton(
            //ボタンを押したら次のページに遷移するようにする
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) {
                  return SecondPage();
                }),
              );
            },
            //ボタンの中に表示するテキスト
            child: Text("次のページへ")),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("2番目の画面")),
      body: Center(
        child: RaisedButton(
            //ボタンを押したら次のページに遷移するようにする
            onPressed: () {
              Navigator.pop(context);
            },
            //ボタンの中に表示するテキスト
            child: Text("前のページへ")),
      ),
    );
  }
}


実装の結果を以下に示します。

「次のページへ」というボタンを押すと...
2番目の画面に遷移することができました。

「前の画面へ」というボタンを押すと...
初期画面に戻ることができました。

一応、画面遷移の実装ができました。

名前付きルートによる遷移

Fluttterには一つ一つの画面にURLのような名前をつけ、その名前を利用して画面遷移を行うことが可能です。
名前付きルートを使用することによって、先程の画面遷移の実装よりも再利用性の高いコーディングが可能になります。
MaterialApp内で以下のプロパティを設定します。この際、homeプロパティが使えなくなるのでご注意ください。
initialRouteプロパティには、アプリが起動した際に表示して欲しい初期画面を指定することが可能です。
routesプロパティには、それぞれのルートと対応するウィジットを指定することができます。
以上で定義したルートをpushNamedメソッド内で使用することが可能です。
先程の「基本的な画面遷移」に比べてpushメソッド内の記述が短くなる上に、ルートをグローバルな変数のように扱うことができるのでとても便利です。

以下に名前付きルートの実装を示します。

import 'package:flutter/material.dart';


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


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext cotext) {
    return MaterialApp(
      title: "Flutter",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
     //名前付きルートの定義
      initialRoute: "/",
      routes: {
        "/": (context) => FirstPage(),
        "/second": (context) => SecondPage(),
      },
    );
  }
}


class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("初期画面")),
      body: Center(
        child: RaisedButton(
            //名前付きルートを使用した画面遷移の部分(pushNamedを使用する)
            onPressed: () {
              Navigator.pushNamed(context, "/second");
            },
            //ボタンの中に表示するテキスト
            child: Text("次のページへ")),
      ),
    );
  }
}


class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("2番目の画面")),
      body: Center(
        child: RaisedButton(
            //ボタンを押したら次のページに遷移するようにする
            onPressed: () {
              Navigator.pop(context);
            },
            //ボタンの中に表示するテキスト
            child: Text("前のページへ")),
      ),
    );
  }
}


上記の実装は「基本的な画面遷移」で実装した挙動と同じものです。

MaterialAppの中でhomeプロパティの設定ができなくなるかわりに、initialRouteによって初期画面の指定がされているので初期画面の描画は問題なく行われます。

新しいルートにデータを送ってみる

画面遷移をする際に、画面間でのデータのやり取りをしたい場面があります。
このセクションでは、「基本的な画面遷移」の実装をもとにデータの受け渡しについて説明していきたいと思います。
以前、onPressedMaterialPageRouteの中のbuilderプロパティで遷移先のページウィジットを指定すると説明しました。
この際、遷移先のページウィジットはコンストラクタによってクラスからインスタンスが生成されます。
このコンストラクタにデータを渡すことによって、遷移先にデータを送ることができます。
以下にコードを示します。

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context){
      //ここのコンストラクタの部分に引数としてデータを渡す
      return SecondPage("渡したいデータ");
    },
  ),
);


先程の実装ですと、SecondPageのコンストラクタは値を受け取るようにしていなかったので以下のように修正する必要があります。

class SecondPage extends StatelessWidget{
  //メンバ変数の定義
  final String data;
  //コンストラクタの定義
  SecondPage(this.data);
}


以上のように実装することでデータを受け渡すことが可能となります。

上記のコードのままですと、受け取れているのかわからないので以下のコードに変更しました。

import 'package:flutter/material.dart';


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


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext cotext) {
    return MaterialApp(
      title: "Flutter",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: FirstPage(),
    );
  }
}


class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("初期画面")),
      body: Center(
        child: RaisedButton(
            //ボタンを押したら次のページに遷移するようにする
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) {
                    //ここのコンストラクタの部分に引数としてデータを渡す
                    return SecondPage("渡したいデータ");
                  },
                ),
              );
            },
            //ボタンの中に表示するテキスト
            child: Text("次のページへ")),
      ),
    );
  }
}


class SecondPage extends StatelessWidget {
  //メンバ変数の定義
  final String data;
  //コンストラクタの定義
  SecondPage(this.data);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("2番目の画面")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            RaisedButton(
                //ボタンを押したら次のページに遷移するようにする
                onPressed: () {
                  Navigator.pop(context);
                },
                //ボタンの中に表示するテキスト
                child: Text("前のページへ")),
            //ここでデータが渡ってきているのかどうかをみる
            Text(this.data),
          ],
        ),
      ),
    );
  }
}

実行結果を以下に示します。

second pageにデータが渡ってきていることがわかりますね。

前のルートに戻る時のにデータを送ってみる

あるページに遷移するとき同様に、ページを戻る場合にもデータを受け渡すことが可能です。
ページを戻る時にデータを渡す場合、pop()メソッドの第2引数にデータを渡します。
以下にコードを示します。

Navigator.pop<String>(context, "戻る際に渡したいデータ");


そして、受け取ったデータはpush()メソッドの戻り値として返ってきます。
以下のように書くことでpush()メソッドの戻り値としてデータを受け取ることができます。

onPressed: () async {
  const data = await Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context){
        return SecondPage();
      }
    )
  );
  print(data);//受け取ったデータをコンソールに出力
}


実装した全体のコードを以下に示します。

import 'package:flutter/material.dart';


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


class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext cotext) {
    return MaterialApp(
      title: "Flutter",
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: FirstPage(),
    );
  }
}


class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("初期画面")),
      body: Center(
        child: RaisedButton(
            //ボタンを押したら次のページに遷移するようにする
            onPressed: () async {
              var data = await Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) {
                    return SecondPage();
                  },
                ),
              );
              print(data); //受け取ったデータをコンソールに出力
            },
            //ボタンの中に表示するテキスト
            child: Text("次のページへ")),
      ),
    );
  }
}


class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("2番目の画面")),
      body: Center(
        child: RaisedButton(
            //ボタンを押したら次のページに遷移するようにする
            onPressed: () {
              Navigator.pop<String>(context, "戻る際に渡したいデータ");
            },
            //ボタンの中に表示するテキスト
            child: Text("前のページへ")),
      ),
    );
  }
}


実行結果を以下に示します。
初期画面です。

「次のページへ」をクリックしました。

「前のページへ」をクリックしました。

コンソール画面に「戻る際に渡したいデータ」と表示されていることから、うまくデータが渡っていることがわかります。

最後に

以上で画面遷移に関する説明を終わります。
お疲れ様でした。

参考文献

・Flutter | Flutter documentation
https://flutter.dev/docs 参照日2021.8.29
・南里 勇気、太田 佳敬、矢田 祐基、片桐 寛貴『Flutter モバイルアプリ開発バイブル』(株式会社リクナビ出版、2019)
・石井幸次 『基礎から学ぶ Flutter』(株式会社シーアンドアール研究所)

© 2021 powerd by UnReact