本記事について
本記事はシリーズ連載記事の第2回になります。
第1回では環境構築が完了しましたので、本記事ではその状態から引き続き進めていきます。
バージョン
デモコードの削除
実装を進める上で、一番大元になるのが lib/main.dart
です。その他アプリで実装していくコードのファイルも lib
ディレクトリ内に作成していくことになります。環境構築をした初期状態だと色々なコメントが記述されていますが、見づらいですので下記コードではコメント部分は削除しています。
lib/main.dart
は下記のように void main()
メソッドから MyApp
を呼ぶ形になっています。
lib/main.dart
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { const MyHomePage({super.key, required this.title}); final String title; @override State<MyHomePage> createState() => _MyHomePageState(); } class _MyHomePageState extends State<MyHomePage> { int _counter = 0; void _incrementCounter() { setState(() { _counter++; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(widget.title), ), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ const Text( 'You have pushed the button this many times:', ), Text( '$_counter', style: Theme.of(context).textTheme.headline4, ), ], ), ), floatingActionButton: FloatingActionButton( onPressed: _incrementCounter, tooltip: 'Increment', child: const Icon(Icons.add), ), ); } }
シミュレータを起動した際に表示されるデモページは class MyApp
内の
home: const MyHomePage(title: 'Flutter Demo Home Page'),
上記の箇所で設定されている MyHomePage
が、 class MyHomePage
以下の箇所で実装されている内容で表示されていることが分かりますね。
今回は初期状態で実装されているページは使わないことにしようと思いますので、
class MyHomePage extends StatefulWidget {
上記の箇所から下のclass MyHomePage関連のコードは削除します。この状態だとエラーが出ていると思いますが、一旦そのままで進めます。
lib/main.dart
import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), ); } }
作成したファイルのインポートをする
main.dart に直接記述していくことも出来るのですが、最終的に画面遷移をさせようと思いますので分かりやすくするために main.dart とは別のファイルを作ってそれをimportする形にします。
今回は画像の一覧を表示させるページを作ろうと思いますので lib
ディレクトリ内に新規に image_list.dart
を作ります。
lib/image_list.dart
import 'package:flutter/material.dart'; class ImageList extends StatelessWidget { const ImageList({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('タイトル')), body: const Text('画像一覧ページです'), ); } }
画面上部のAppBarには「タイトル」、bodyには「画像一覧ページです」だけを表示させる簡単なページです。
このページを表示させるために、 lib/main.dart
にこのファイルをインポートします。インポートするには lib/main.dart
に
import 'image_list.dart';
を追加します。パスは相対パスで記述出来るので、同じlibディレクトリ配下なので上記の形になります。そして、
- home: const MyHomePage(title: 'Flutter Demo Home Page'), + home: const ImageList(),
上記のようにhomeの引数でMyHomePageを渡していた箇所を、インポートしたImageList()を渡す形に変更します。変更後の lib/main.dart
は下記になります。
lib/main.dart
import 'package:flutter/material.dart'; import 'image_list.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const ImageList(), ); } }
この状態で flutter run
を実行してシミュレータで表示させると、画面上部のAppBarには「タイトル」、bodyには「画像一覧ページです」が無事表示されました。
ImageListのページを表示出来ることが確認出来ましたので、現在テキストだけが表示させているところを画像表示させるように変更しましょう。
Widgetを使う
ではここからは具体的にWidgetを使って実装をしていきましょう。FlutterではUIを実装するにあたり、美しくかつ迅速に実装できるように最初から部品を用意してくれていて、その部品のことをWidgetと言います。
と言っても、今までの実装で既にWidgetは使ってきています。なぜならFlutterではUIに関わる部分はWidgetであるからです。例えば Text('画像一覧ページです')
と実装してテキストを表示させているこの Text()
もWidgetですし、「タイトル」と文字を表示させている上部の AppBar()
もWidgetです。
Widgetの使い方はとても直感的で、あるWidgetの中に別のWidgetを入れることで親子構造を作ることが出来ます。例えば、
- 画像を表示させるWidgetを、項目をグリッド表示させるWidgetの中に入れることで「画像をグリッド表示させる」という実装が出来る
- 画像を表示させるWidgetを、ピンチイン/ピンチアウトで項目を拡大縮小表示出来るWidgetの中に入れることで「画像をピンチイン/ピンチアウトで拡大縮小表示出来るようにする」という実装が出来る
上記のような形で使うことができます。
ちなみにWidgetについては公式ドキュメントに Widget catalog がありますのでそちらでどういうWidgetがあるかを確認することが出来ますので、ざっと見てみるのも良いと思います。
それでは例で挙げた画像表示に関するWidgetの親子構造を使った上記の2点について、実際に実装して確認していきましょう。まずは
こちらをやってみましょう。
最初に画像をWidgetで表示させようと思います。画像表示の方法はいくつかあるのですが、簡単にやれるのでここではインターネット上にアップロードされている画像を表示させる方法でやってみましょう。
例えばmofmof技術部で表示されているmofmofのロゴ画像のURLは https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png ですので、これを表示させてみましょう。
lib/image_list.dart
の下記部分を変更してください。 Image
Widgetを使います。やることは簡単で、Widgetが持っているconstructorメソッドに画像URLを引数として渡してあげるだけです。
lib/image_list.dart
- body: const Text('画像一覧ページです'), + body: Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'),
こんな感じで画像が表示されました。では次にグリッド表示をさせてみましょう。グリッド表示をさせるには GridView
Widgetを使います。
実装後の lib/image_list.dart
と表示される画面は下記です。
lib/image_list.dart
import 'package:flutter/material.dart'; class ImageList extends StatelessWidget { ImageList({super.key}); @override Widget build(BuildContext context) { final images = [ Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), ]; return Scaffold( appBar: AppBar(title: const Text('タイトル')), body: GridView.count(crossAxisCount: 2, children: images), ); } }
まず変数imagesを宣言して Image
Widgetを6つ配列に格納します。その変数imagesを GridView.count
の引数に渡してあげます。単体のWidgetを渡す場合はキーは child
になるのですが、今回は配列で複数のWidgetを渡すのでキーが children
になっています。 crossAxisCount
のキーは画面上に何列で表示させるかを指定するもので、ここで2を指定しているので画面では2列で表示されます。これを3に変えると
上記のように3列で表示されます。分かりやすいですね。
GridView
には GridView.count
以外にも GridView.extent
や GridView.builder
などがあり、用途によって使い分けることが出来ますので、色々と試してみるのが良いと思います
。
では次に
こちらをやってみましょう。
実はここまで画像を表示させてきた Image.network()
のWidgetだけでは、画面上でピンチイン/ピンチアウトをしても画像の拡大/縮小は出来ません。そのため、ピンチイン/ピンチアウトが有効になるWidgetの中に Image.network()
を入れる、というやり方をすることになります。
lib/image_list.dart
はグリッド表示をさせている状態になっているかと思いますが、一旦画像を1枚だけ表示させる状態に戻します。
シミュレータ上でmacのキーボードのoptionを押すと2つの丸が表示されます。この状態でマウスを左クリックしたままマウスを動かすとシミュレータ上でピンチイン/ピンチアウトの動作を行うことが出来ます。ただoptionを押した状態だけだと画面真ん中の位置からピンチイン/ピンチアウトをする形になるのですが、optionを押したままさらにshiftを押すとピンチイン/ピンチアウトを行う位置自体を変えられますので、表示されている画像上に丸が当たる状態にしてからピンチイン/ピンチアウトをしましょう。
やってみると、 Image.network()
のWidgetだけでは画面上でピンチイン/ピンチアウトをしても何も変化が無いことが分かると思います。
では、ピンチイン/ピンチアウトが出来るようにしましょう。そのためには、 InteractiveViewer
Widgetを使います。
lib/image_list.dart
import 'package:flutter/material.dart'; class ImageList extends StatelessWidget { const ImageList({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('タイトル')), body: InteractiveViewer( child: Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), ), ); } }
Image.network()
のWidgetを InteractiveViewer
の中に入れます。
このように、ピンチイン/ピンチアウトが出来るようになりました。
flutterには様々なWidgetがありますので、用途に合わせて上手く使っていきましょう。
画面遷移をさせる
環境構築後最初に表示されるデモページのように1枚の画面に機能が全て詰まっているアプリであれば画面遷移は不要ですが、複数のページで機能を分けるようなアプリの場合は画面遷移をさせたいことが多くあると思います。
画面遷移をさせる方法はいくつかあるのですが、複数のページを移動させる場合はRoutesを使う方法が個人的に使いやすかったですので、今回はRoutesを使う方法をご紹介します。
まず、現在の lib/main.dart
を見てみましょう。
lib/main.dart
import 'package:flutter/material.dart'; import 'image_list.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const ImageList(), ); } }
MaterialApp
Widgetの引数のhomeでImageList()を渡すことで画面を表示させていますね。
ここを下記のように変更します。
- home: const ImageList(), + routes: { + '/': (context) => const ImageList(), + },
再度シミュレータを開いても、同じように画像が表示された画面が表示されることが分かると思います。
アプリ起動時に最初に表示される画面はroutesでは '/'
で設定することが出来、ここで設定したものがアプリ起動時に表示されることになります。
先ほど画像をグリッド表示させる画面の実装と画像のピンチイン/ピンチアウトをさせる画面の実装を行いましたので、 lib
ディレクトリの中にグリッド表示の画面を image_list.dart
、ピンチイン/ピンチアウトの画面を image_detail.dart
として作成しましょう。 lib/main.dart
のroutesは下記のように変更します。
routes: { '/': (context) => const ImageList(), '/image_detail': (context) => const ImageDetail(), },
新しく作成した ImageDetail
はimportされていないのでエラーが出ると思いますので、
import 'image_detail.dart';
も追記しておきましょう。
これで遷移前と遷移後の画面が用意出来ましたので、画面遷移の処理を実装しましょう。
画面遷移をさせるには、 Navigator
Widgetを使います。 Navigator
には色々なメソッドがあるのですが、今回のroutesを使った画面遷移の方法の場合は Navigator.pushNamed()
を使います。
Navigator.pushNamed(context, '/image_detail',)
上記のように第1引数にcontext、第2引数にroutesの名前を渡してあげます。この context
というのはclassを作成する際に記載している
class ImageList extends StatelessWidget { ImageList({super.key}); @override Widget build(BuildContext context) {
ここで引数として渡されている BuildContext context
のことです。contextについてはここでは細かいことは割愛させて頂きますが、このページを表示させるのに必要な情報が格納されているものだというイメージを持って頂けたら良いかと思います。
Navigator
Widgetは画面遷移をさせる処理を行うだけのWidgetですので、これ自体には「画面をタップしたら」というような動作は設定出来ません。そこで、画面をタップした際の動作を設定するために GestureDetector
Widgetを使います。
GestureDetector( onTap: () { // タップ時に行いたい処理 }, child: , // タップ範囲として設定するWidgetをchildに設定する ),
GestureDetector
を上記のように設定することで、childとして設定したWidgetをタップした際に指定の処理を行うことが出来ます。
では実際に Navigator
と GestureDetector
を lib/image_list.dart
実装したのが下記です。
lib/image_list.dart
import 'package:flutter/material.dart'; class ImageList extends StatelessWidget { ImageList({super.key}); @override Widget build(BuildContext context) { final images = [ GestureDetector( onTap: () { Navigator.pushNamed( context, '/image_detail', ); }, child: Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), ), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), Image.network('https://tech.mof-mof.co.jp/_nuxt/img/63792f2.png'), ]; return Scaffold( appBar: AppBar(title: const Text('タイトル')), body: GridView.count(crossAxisCount: 2, children: images), ); } }
imagesで宣言している6つの画像のうち、最初の1つにだけ Navigator
と GestureDetector
を実装しています。シミュレータの動作はどうなるでしょうか。
最初の画像以外をタップしても何も変化はせず、最初の画像をタップした時のみ画面遷移が出来ていることが分かりますね。また、画面遷移後は左上の「<」をタップすることで元の画面に戻ることも出来ています。
画面左上の「<」で元の画面に戻る部分は AppBar
のWidgetが自動的に表示させているものになるのですが、アプリの仕様によっては画面遷移後に元の画面に戻したくない場合もあると思います。例えばログイン画面のような画面ですね。
そういう場合は、同じ Navigator
Widgetの別のメソッドを使うことで一方通行の画面遷移を実現出来ます。
- Navigator.pushNamed(context, '/image_detail',) + Navigator.of(context).pushReplacementNamed('/image_detail',);
上記の形で、 Navigator.pushNamed
を Navigator.of(context).pushReplacementNamed
に置き換えてみましょう。シミュレータの動作はどうなるでしょうか。
画面遷移後に左上の「<」が表示されなくなり、元の画面には戻れなくなりました。
これで画面遷移については一通り出来るようになりましたね。
最後に
今回はいくつかのWidgetの使い方の紹介と画面遷移の方法をご紹介しました。どのWidgetを使えば自分のやりたいことが実現できるか、というところについては都度調べながら実装していく必要はありますが、使うWidgetさえ決まればあとはパズルを組んでいくような感覚で実装していけるというのはflutterの面白みだなと思います。