FlutterのNavigatorの使い方と仕組み


はじめに

この記事では、Navigator Widgetについて解説します。(以下、Navigatorとします)
以下の2部構成です。

  1. Navigatorの使い方
  2. Navigatorの仕組み

ただし、この記事では、遷移時のアニメーションの作り方については触れていません。ご注意ください。

この記事のサンプルコードは以下のページに置いています。
https://github.com/HeavenOSK/basic_navigator

Navigatorとは

NavigatorはFlutterでページ遷移を実装する際に使用するWidgetです。

Navigatorの使い方

Navigatorの使い方について解説します。

解説する内容は、以下の通りです。

  1. 進む遷移をして、戻る遷移を行う
  2. 戻る遷移の直後に、処理を行う
  3. 戻る遷移の際に、値を受け取る

1.遷移して戻る

最もシンプルで、よく用います。

ボタン押下など、ユーザ操作のコールバックに Navigatorの処理を書きます。
進む遷移ではpushメソッドを、戻る遷移ではpopメソッドを用います。

first_page.dart
RaisedButton(
  child: Text('Next'),
  onPressed: () {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) {
          return SecondPage();
        },
      ),
    );
  },
)
second_page.dart
RaisedButton(
  child: Text('Back'),
  onPressed: () {
    Navigator.of(context).pop();
  },
)

サンプルコードでは、pushメソッドの引数としてMaterialPageRouteを指定しています。これは、遷移時にMaterialDesignに則ったアニメーションを行うための指定です。
MaterialPageRouteCupertinoPageRouteと書き換えれば、iOS風のアニメーションで遷移します。

またMaterialPageRouteでは、builderプロパティの返り値として、SecondPageを指定しています。これは遷移先のページの指定です。
SecondPageThirdPageと書き換えれば、ThirdPageへの遷移になります。

2. 戻る遷移の直後に、処理を行う


遷移して戻った直後に、何か処理を行う場合に用います。

pushメソッドの前にawaitキーワードを書きます。
(コールバックの先頭にasyncキーワードを書くのを忘れないようにします)

pushメソッド以下の処理は、戻る遷移の後実行されるようになります。
以下のサンプルコードでは、戻る遷移の直後にshowDialogメソッドが実行されます。

first_page.dart
RaisedButton(
  child: Text('Next'),
  onPressed: () async {
    await Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) {
          return SecondPage();
        },
      ),
    );
    showDialog(
      context: context,
      builder: (context) => SampleDialog(),
    );
  },
),

3. 戻る遷移の際に、値を受け取る

遷移時に、フォームの入力情報などを受け渡す際に使います。

前項で紹介した「戻る遷移の直後に、処理を行う」ことが可能なのは、pushが遅延処理を行うメソッドだからです。
pushメソッドの返り値はFutureクラスで、遷移時の値の受け渡しに用います。
 
pushメソッドの前にawaitキーワードを書き、変数に代入することで、戻る遷移の際にpopメソッドから渡された値を用いることができます。
以下では、受け取った文字列をshowDialogで表示しています。

first_page.dart
RaisedButton(
  child: Text('Next'),
  onPressed: () async {
    final result = await Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) {
          return SecondTextInputPage();
        },
      ),
    );
    if (result != null) {
      final contentText = 'I received ' + result + ' !';
      showDialog(
        context: context,
        builder: (context) {
          return SampleDialog(
            contentText: contentText,
          );
        },
      );
    }
  },
)

popメソッドでは、受け渡したい値を引数に指定します。
以下のサンプルコードでは、テキストフィールドで入力された文字列をpopメソッドの引数で渡しています。(複数の値を受け渡しすることも可能です。)

second_text_input_page.dart
String inputValue = '';

TextField(
  onChanged: (value) {
    inputValue = value;
  },
  onEditingComplete: () {
    Navigator.of(context).pop(inputValue);
  },
)

まとめ

Navigatorの基本的な使い方を見ました。

「Navigatorの使い方」は以上です。

2. Navigatorの仕組み

ここから先は、Navigatorの仕組みについて解説します。

Flutter の公式ドキュメントの Navigator のページの冒頭には、以下のように書いています。

A widget that manages a set of child widgets with a stack discipline.

https://docs.flutter.io/flutter/widgets/Navigator-class.html

スタック規律を使用して子ウィジェットのセットを管理するウィジェット。(訳:Google翻訳)

つまり、Navigatorは、「スタックの仕組みを使って、子Widgetの集まりを管理する Widget」 です。

スタックは「後入れ先出し(一番最後に入れたものを出す)」のデータ構造です。
よって、「一つ前に訪れたページに戻る」 というページ遷移の実装に適しています。

スタック (wikipedia)
https://ja.wikipedia.org/wiki/%E3%82%B9%E3%82%BF%E3%83%83%E3%82%AF

ところで、Flutterに触れたことがある人なら、スタックと聞いて思い出すWidgetがあると思います。 

そうです。Stack Widgetです。

Stack

Stack Widgetは複数のWidgetを重ねて表示する際に使用する Widget です。(以下、Stack とします)
StackchildrenプロパティにWidgetのリストを指定することで、複数のWidgetを重ねて表示できます。 

以下の例では、BottomWidgetTopWidgetが重ねて表示されます。

stack.dart
  Stack(
    children: <Widget>[
      BottomWidget(),
      TopWidget(),
    ],
  )

Navigatorによる遷移は、データ構造としてのスタックだけではなく、このStack Widgetを用いて実現されています。

遷移時の処理の流れ

遷移時にStack上にWidgetを重ねる処理は、Overlay Widgetが担当します。

「Navigatorの使い方」で用いたサンプルコードを再掲します。
以下のサンプルコードでは、FirstPageからSecondPageへの遷移を行っています。

first_page.dart
class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: RaisedButton(
        child: Text('Next'),
        onPressed: () {
          Navigator.of(context).push(
            MaterialPageRoute(
              builder: (context) {
                return SecondPage();
              },
            ),
          );
        },
      ),
    );
  }
}

上記のサンプルコードでpush()pop()を行ったと仮定して、処理の流れを追ってみます。

※以下、「Overlayが管理するStack」を、単にStackと書きます。

FirstPageからSecondPageへpush()を行う時

(0.StackにFirstPageのみ追加されている状態)
1.Stackの最上部にSecondPageを追加して、画面外に配置する
2.SecondPageをアニメーションして、画面前面に移動させる
3.アニメーション終了後、StackからFirstPageをoffstageに追加する
(※offstageは、Overlayの内部で使われている、現在描画する必要はないが、将来描画する予定があるWidgetのリストです)

SecondPageからFirstPageへpop()を行う時

(0.StackにSecondPageのみ追加されている状態)
1.StackのSecondPageの下層にFirstPage追加する
2.画面前面のSecondPageをアニメーションして、画面からはずれるように移動させる
3.アニメーション終了後、StackからSecondPageを除外して、Overlayの管理からも除外する
(※現在描画する必要がなく、将来描画する予定もないWidgetはOverlayの管理から除外されます)

基本的には以上の流れで、Navigatorによる遷移が行われます。
このような管理を行うことで、描画の負荷を下げています。

まとめ

Stackの観点から、Navigatorによる遷移の仕組みを見ました。

「Navigatorの仕組み」は以上です。

おわりに

ここまで読んでいただき、ありがとうございました。

誤っている点や分かりにくい点にお気づきになられましたら、お手数ですがTwitterアカウントまでお願い致します。

HeavenOSK