Closed12

flutterでの画面遷移方法がわからない

ホーム画面と設定画面を作った。ホーム画面のAppBar上にある設定ボタンをタップしたら設定画面へ遷移したい。


ネイティブでは pushViewController(_:animated:)startActivity() を使って遷移する。これらの方法は命令的遷移と呼ぶらしく Flutter にも同様の機能があるが Navigator 1.0 と呼ばれている。現在の主流またはこれから主流になっていくのは Navigator 2.0 (Router)であるらしい。Routerは宣言的遷移と呼ぶらしい。

宣言的遷移なるものはよくわかっていないが、AndroidでいうところのNavigation コンポーネント((nav_graph.xml に画面の定義を書いておいて、そこで定義したidを使って画面遷移する))を使った画面遷移に似たようなものだろうか。

Flutterは「Write once, run anywhere」なので、モバイルやらウェブやら色んな環境で動く。Navigator 2.0 ではさまざまな環境とフォローアップするために理解が難しい。そこで出てきたのが go_routerで Navigator 2.0を簡単に扱えるようになったようだ。

他にも beamerroutemaster などたくさんの画面遷移ライブラリがあるが、基本は go_router を使えば良いようだ。そういう意味では熱気のある Flutter のなかでもさらに熱のあるジャンルだろう。

さて Flutterでの画面遷移に go_router を使うことはわかった。

pubspec.yamlgo_router を追加した。

dependencies:
  flutter:
    sdk: flutter
  go_router: ^3.1.1

MyApp または Appクラスにある(テンプレで生成されていた) build 処理を書き換えた。

class App extends StatelessWidget {
  App({Key? key}) : super(key: key);

  // // This widget is the root of your application.
  // @override
  // Widget build(BuildContext context) {
  //   return MaterialApp(
  //     title: 'Flutter Demo',
  //     theme: ThemeData(
  //       primarySwatch: Colors.blue,
  //     ),
  //     home: const MyHomeScreen(title: 'Flutter Demo Home Page'),
  //   );
  // }

  
  Widget build(BuildContext context) => MaterialApp.router(
    routeInformationParser: _router.routeInformationParser,
    routerDelegate: _router.routerDelegate,
    title: 'GoRouter Example',
  );

  final GoRouter _router = GoRouter(
    routes: <GoRoute>[
      GoRoute(
        name: "home",
        path: '/',
        builder: (BuildContext context, GoRouterState state) =>
          const MyHomeScreen(title: 'Flutter Demo Home Page')
        ,
      ),
      GoRoute(
        name: "settings",
        path: '/settings',
        builder: (BuildContext context, GoRouterState state) =>
          const SettingsScreen(),
      ),
    ],
  );

ホーム画面の設定ボタンをタップした時のイベントハンドラは既に実装済みである。context.go('/settings'); のように遷移したい path を指定すれば遷移できる。

    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(widget.title),
        actions: [
          IconButton(
            icon: const Icon(Icons.settings),
            tooltip: '設定',
            onPressed: () {
              // 設定画面へ遷移する
              context.go('/settings');
            },
          )
        ],
      ),

設定ボタンをタップしたら設定画面に遷移できるようになった。

ただし問題はまだ残っている。設定画面からホーム画面への戻りの遷移ができない。UINavigationControllerであればpushで画面遷移すると戻るボタンがついているはずだが、go_router (というかNavigator 2.0)ではそこまでサポートしてくれないのだろうか。

ちなみにAndroidでテストしたところ設定画面でハードウェアキーの戻るボタンを押下するとアプリが終了してしまった。

go_router で遷移すると遷移スタックに画面が積まれるのかと思ったがそうではないようだ。または別の方法があるのかもしれない。

元の画面に戻りたいのであれば階層的ルーティングを使う必要がある。設定画面をサブルート(Sub-routes)として定義する。

NG:設定画面からホーム画面に戻れない

ホーム画面と設定画面が同列になっている。

  final GoRouter _router = GoRouter(
    routes: <GoRoute>[
      GoRoute(
        name: "home",
        path: '/',
        builder: (BuildContext context, GoRouterState state) =>
          const MyHomeScreen(title: 'Flutter Demo Home Page')
        ,
      ),
      GoRoute(
        name: "settings",
        path: '/settings',
        builder: (BuildContext context, GoRouterState state) =>
          const SettingsScreen(),
      ),
    ],
  );

OK:設定画面からホーム画面に戻れる

設定画面をホーム画面の子画面として定義した。

  final GoRouter _router = GoRouter(
    routes: <GoRoute>[
      GoRoute(
        name: "home",
        path: '/',
        builder: (BuildContext context, GoRouterState state) =>
          const MyHomeScreen(title: 'Flutter Demo Home Page')
        ,
        routes: [
          GoRoute(
            name: "settings",
            path: 'settings',
            builder: (BuildContext context, GoRouterState state) =>
              const SettingsScreen()
            ,
          ),
        ]
      ),
    ],
  );

こうすることで設定画面に行って戻ってこれるようになった。

さくさんは設定画面はモーダルで表示したい派閥である。モーダル表示にはできるのであろうか?

設定画面はモーダルで表示したい派閥である。モーダル表示にはできるのであろうか?

Flutter的に「iOSのモーダル表示はない」と考えた方がよさそう。ただしトランジションをカスタムするなどした「iOS風のモーダル表示はある」。

コンテナ(Flutter的にこんな呼び方なのかわからないけど)を二重に用意すれば、背景となっている画面そのままで、モーダルトランジションで表示した画面のなかでさらに画面遷移をさせることはできそう。

パラメータを必要とする画面への遷移はどうするのか?渡された id を表示するだけの「詳細画面」を作ってみた。この詳細画面には、ホーム画面と設定画面から遷移することができる。

  final GoRouter _router = GoRouter(
    routes: <GoRoute>[
      GoRoute(
        name: "home",
        path: '/',
        builder: (BuildContext context, GoRouterState state) =>
          const MyHomeScreen(title: 'Flutter Demo Home Page')
        ,
        routes: [
          GoRoute(
            name: "settings",
            path: 'settings',
            builder: (BuildContext context, GoRouterState state) =>
              const SettingsScreen()
            ,
          ),
          GoRoute(
            name: "detail",
            path: "detail/:pid",
            builder: (BuildContext context, GoRouterState state) {
              final String id = state.params['pid']!;
              return DetailScreen(pid: id);
            },
          )
        ]
      ),
    ],
  );

homeの子としてdetailを定義している。なのでホーム画面から詳細画面、詳細画面からホーム画面への戻る遷移は問題ない。

ホーム画面から設定画面、設定画面から詳細画面への遷移はできた。ただ詳細画面で戻るボタンを押すとホーム画面に戻ってしまう問題が発生した。

例えば /items/history から /item/:pid へ遷移して、元の画面に戻るのはウェブでも普通にできる。何らかの方法があるんだろうけど go_router ではどうしたらよいものか……と頭を抱えていた。

そもそも「go_router」なので画面遷移の方法が context.go() しかないと思い込んでたのが間違いだった。context.push() なるメソッドの存在に気付いたので解決できた。push は遷移スタックに積み上げてくれる。

  • go は、 ルーターで宣言したロケーションに従って順番にスタックを構築する。
  • pushは、遷移スタックにひとつ画面を追加する。

https://zenn.dev/link/comments/38b81db25e2488 で定義したルート指定では以下のようになっていた。

  • home
    • settings
    • detail

「home to settings」または「home to detail」は、 go でも push でも遷移できるし、戻りもサブルートの順番に戻れば良いため正しく動いているように見える。

ただし、「settings to detail」は同格のサブルートにあたるため、settingsからdetail へ go した時点で履歴スタックが detail に移動してしまう (戻り先が home にすげ替えられてしまう)。

「settings to detail」の遷移後に settings へ戻りたい場合には push を使わないければいけない。

HomeScreen から SettingsScreen へ遷移する時、iOS でいうところのプッシュ的なトランジションで移動する場合は以下の通り。

  final GoRouter _router = GoRouter(
    routes: <GoRoute>[
      GoRoute(
          name: "home",
          path: '/',
          builder: (BuildContext context, GoRouterState state) =>
            const HomeScreen(title: 'Flutter Demo Home Page')
          ,
          routes: [
            GoRoute(
              name: "settings",
              path: 'settings',
              builder: (BuildContext context, GoRouterState state) =>
                const SettingsScreen()
              ,
            ),
          ]
      ),
    ],
  );

HomeScreen から SettingsScreen へ遷移する時、iOSで言うところのモーダル的なトランジションで遷移する場合は以下の通り。

  final GoRouter _router = GoRouter(
    routes: <GoRoute>[
      GoRoute(
          name: "home",
          path: '/',
          builder: (BuildContext context, GoRouterState state) =>
            const HomeScreen(title: 'Flutter Demo Home Page')
          ,
          routes: [
            GoRoute(
              name: "settings",
              path: 'settings',
              pageBuilder: (context, state) => const MaterialPage(
                fullscreenDialog: true,
                child: SettingsScreen(),
              ),
            ),
          ]
      ),
    ],
  );
このスクラップは3ヶ月前にクローズされました
ログインするとコメントできます