[Flutter] スタックナビゲーション

2021-07-26 hit count image

Flutterを使ってアプリを開発しています。このブログポストではFlutterで生成したプロジェクトにスタックナビゲーションを使う方法について説明します。

概要

Flutterを使ってアプリを開発しています。アプリ開発で画面の移動にはナビゲーションを使います。今回のブログポストではFlutterでスタックナビゲーションを使って画面を移動する方法について説明します。

このブログポストで紹介するソースコードは下記のリンクで確認できます。

Stack

スタックナビゲーションは画面の上に画面を表示する方式で画面を移動します。画面の上に画面を表示する時はpushを、上に表示された画面を削除する時にはpopを使います。

それでは例題を見てスタックナビゲーションを理解してみましょう。次のコマンドを使ってスタックナビゲーションのためプロジェクトを生成します。

flutter create my_app
cd stack

次はmain.dartファイルを開いて下記のように修正してスタックナビゲーションを使ってみます。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: Home(),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Navigator'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Second Screen'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (_) => SecondScreen(),
              ),
            );
          },
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Home Screen'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}

そしたらソースコードを一つづつ詳しくみてみましょう。

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      home: Home(),
    );
  }
}

ここまでは基本的アプリを生成することと同じです。MaterialAppを使うと、アプリが実行された時、一番最初表示される画面をhomeパラメータに設定することができます。ここに私はHomeウィジェットを設定しました。

次はスタックナビゲーションを使うため二つのStatelessWidgetを生成しました。一つはMaterialApphomeに設定したHomeウィジェットとスタックナビゲーションを使ってHomeウィジェット上に表示するSecondScreenウィジェットを生成しました。

class Home extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: ...,
      body: Center(
        child: ElevatedButton(
          child: Text('Second Screen'),
          onPressed: () {...},
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar:...,
      body: Center(
        child: ElevatedButton(
          child: Text('Home Screen'),
          onPressed: () {...},
        ),
      ),
    );
  }
}

二つのウィジェットはScaffoldを使って基本的同じ構造を持っています。画面の真ん中にElevatedButtonウィジェットを使ってボタンを表示しました。そして、各ボタンを押した時、スタックナビゲーションを使って画面の移動を実装しました。

次は各ボタンを押した時、スタックナビゲーションを呼び出す部分をみてみましょう。一旦Home画面からSecondScreenに移動するコードをみてみましょう。

onPressed: () {
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => SecondScreen(),
    ),
  );
},

スタックナビゲーションで、新しい画面を画面の上に表示するためにはNavigatorウィジェットのpush関数を使う必要があります。push関数をコールする時、ウィジェットツーリの位置情報を持ってるcontextMaterialPageRouteのbuilderを使って画面の上に表示するウィジェットをパラメータで設定します。

このようにコードを作成するとHomeウィジェットに表示されたボタンを押すと、Home画面の上に、SecondScreenウィジェットが表示されます。

次はSecondScreenHome画面に戻るため、SecondScreenを削除するコードを確認してみましょう。

onPressed: () {
  Navigator.pop(context);
},

Navigatorウィジェットのpopをコールすると、現在表示された画面が削除されるし、現在表示された画面の下にあった画面が表示されます。pop関数をコールする時には現在位置情報を持ってるcontextを渡さなきゃならないです。

Named routes

Flutterで複数画面を管理する時、Named routesを使います。

次はNamed routesを使ってスタックナビゲーションを使ってみましょう。まず、main.dartファイルを次のように修正します。

import 'package:flutter/material.dart';
import 'ScreenA.dart';
import 'ScreenB.dart';
import 'ScreenC.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      debugShowCheckedModeBanner: false,
      initialRoute: 'ScreenA',
      routes: {
        'ScreenA': (context) => ScreenA(),
        'ScreenB': (context) => ScreenB(),
        'ScreenC': (context) => ScreenC(),
      },
    );
  }
}

Named routesを使うためにはMaterialApproutesに画面の名前と名前に合う画面のウィジェットを宣言します。また、homeパラメータの代わりでinitialRouteを使ってアプリが実行された後最初表示されるウィジェットを定義します。

今回の例題では各画面ウィジェットをlib/ScreenA.dartlib/ScreenB.dartlib/ScreenC.dartファイルを生成した後、そのファイルにコードを作成しました。

このように生成したウィジェットをmain.dartファイルに次のようにインポートしました。

import 'ScreenA.dart';
import 'ScreenB.dart';
import 'ScreenC.dart';

各ファイルのコードをもっと詳しくみてみましょう。ScreenA.dartファイルを内容は次のようです。

import 'package:flutter/material.dart';

class ScreenA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Screen A'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: Text('Screen B'),
              onPressed: () {
                Navigator.pushNamed(context, 'ScreenB');
              },
            ),
            ElevatedButton(
              child: Text('Second C'),
              onPressed: () {
                Navigator.pushNamed(context, 'ScreenC');
              },
            ),
          ],
        ),
      ),
    );
  }
}

StatelessWidgetを継承して、Scaffordウィジェットを使った簡単な画面のウィジェットです。上で説明したソースコードと同じ構造なので説明は省略します。ここではNamed routesを使ったスタックナビゲーションを使う部分だけ説明します。

onPressed: () {
  Navigator.pushNamed(context, 'ScreenB');
},

Named routesを使ってスタックナビゲーションを使うためにはNavigatorウィジェットのpushNamed関数を使います。この時ウィジェットツーリの情報を持ってるcontenxtと移動したい画面の名前をパラメータで渡します。

ScreenB.dartScreenC.dartは同じ構造を持ってますので、コードだけ共有します。

  • ScreenB.dart
import 'package:flutter/material.dart';

class ScreenB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Screen B'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: Text('Back'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            ElevatedButton(
              child: Text('Second A'),
              onPressed: () {
                Navigator.pushNamed(context, 'ScreenA');
              },
            ),
            ElevatedButton(
              child: Text('Second C'),
              onPressed: () {
                Navigator.pushNamed(context, 'ScreenC');
              },
            ),
          ],
        ),
      ),
    );
  }
}
  • ScreenC.dart
import 'package:flutter/material.dart';

class ScreenC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Screen C'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: Text('Back'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
            ElevatedButton(
              child: Text('Second A'),
              onPressed: () {
                Navigator.pushNamed(context, 'ScreenA');
              },
            ),
            ElevatedButton(
              child: Text('Second B'),
              onPressed: () {
                Navigator.pushNamed(context, 'ScreenB');
              },
            ),
          ],
        ),
      ),
    );
  }
}

Named routesを使うともっと簡単に画面の移動することができるし、色んな画面を管理するのに適しています。

popUntil

スタックナビゲーションを使うと、たくさんの画面がたまることがあります。このように溜まった画面を一回で戻るためにはpopUntilを使います。

popUntilの使い方を確認するため、ScreenC.dartファイルを次のように修正します。

import 'package:flutter/material.dart';

class ScreenC extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Screen C'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              child: Text('Home'),
              onPressed: () {
                Navigator.popUntil(context, (route) => route.isFirst);
              },
            ),
          ],
        ),
      ),
    );
  }
}

画面を最初の画面であるScreenAまで戻るため、ScreenCでは次のようなコードを使いました。

onPressed: () {
  Navigator.popUntil(context, (route) => route.isFirst);
},

最初の画面に戻るためNavigatorpopUntilを使いましたし、route.isFirstを設定しました。このようにpopUntilを使うと最初の画面に戻ることができます。または、下記のように使うと特定した画面まで戻ることもできます。

onPressed: () {
  Navigator.popUntil(context, ModalRoute.withName('ScreenA'));
},

上のように使うと、Named routesに定義した名前を使ってその画面まで戻ることができます。

automaticallyImplyLeading

Flutterでスタックナビゲーションを使って画面を移動すると、特に設定をしなくても左上に以前の画面に戻れるボタンが生成されることが確認できます。

普通はこの機能を使うので、特に問題ないですが、時々この機能を使いたくない時もあります。この時、使えることがautomaticallyImplyLeadingオプションです。

次のようにAppBarウィジェとのautomaticallyImplyLeadingオプションを使うと、自動で生成される戻るボタンを消すことができます。

AppBar(
  ...
  automaticallyImplyLeading: false,
)

Swipe back

Flutterでスタックナビゲーションを使うと、基本的スワイプで戻る(Swipe back)機能を使って以前の画面に戻ることができます。

もし、この機能を使いたくない場合、次のようにWillPopScopeウィジェットのonWillPopを使ってスワイプで戻る機能を無効化することができます。

@override
Widget build(BuildContext context) {
  return WillPopScope(
    onWillPop: () async => false,
    child: Scaffold(
      ...,
    ),
  );
}

上のようにスタックナビゲーションを使って表示される画面をWillPopScopeウィジェットにつつみむ後、onWillPopパラメータをfalseで設定したら、スワイプで戻る機能を無効化することができます。

フォーカス

ナビゲーションを使う時、現在の画面(A)から他の画面(B)を表示した後、また現在の画面(A)に戻る時、特定な関数をコールしたり何かの動作を実行したい時があります。この時、次のようにpushNamed関数を使って解決することができます。

Navigator.pushNamed(context, 'B').then((value) {
  // Do something after widget is focused(visible).
});

このようにpushNamed関数使って次のページへ移動する時、thenを使って次のページがpopされた後、特定な動作を実行できるコールバック(Callback)関数を登録することができます。

フルモーダル

基本的スタックナビゲーションは右から左にスライドしながら画面に表示されます。次のコードを使うとスタックナビゲーションを下から上にスライドしながら表示させることができます。

import 'package:flutter/cupertino.dart';
...
Navigator.push(
  context,
  CupertinoPageRoute(
    fullscreenDialog: true,
    builder: (_) => SecondScreen(),
  ),
);

完了

これでFlutterでスタックナビゲーションを使う方法についてみてみました。スタックナビゲーションはアプリの画面の移動に一番多く使えるので、今回よく覚えておきましょう。

私のブログが役に立ちましたか?下にコメントを残してください。それは私にとって大きな大きな力になります!

Posts