Step3 の概要
Step3 では、Navigator を使って 2 つのページを遷移させます。
本章で学べること
- Dart ファイルの分割の方法
- Navigator での画面遷移の方法
- ListView の効果的な使い方
画面遷移を実装しよう
ポケモン図鑑のイメージは、まずメイン画面はリストになっていて、選択すると詳細が表示されるようなイメージですね。
Step3 ではそれを作ってみましょう。
メイン画面と詳細画面の移動については Navigator を使用します。
まずはメイン画面と先に作ったポケモンの画面を分けていきます。
.
├── main.dart
└── poke_detail.dart
PokeDetail ウィジェットを poke_detail.dart にうつします。
main.dart からは import './poke_detail';
で取り込みます。ここを相対パスではなくパッケージパスにした方が良いということもありますが、とりあえずこれでいきます。
この import パスについては Linter でルールを設定できます。Linter については別冊をお待ちください。
main.dart ではかわりに TopPage ウィジェットを作成し、中央にボタンをひとつ配置します。そのボタンをクリックすると、PokeDetail に遷移するようにします。
class TopPage extends StatelessWidget {
const TopPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: const Text('detail'),
onPressed: () => {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => const PokeDetail(),
),
),
},
),
),
);
}
}
Navigator v1 の push/pop のみでいったん作成します。Navigator には色々なページ遷移の作り方があるので、実はちょっと難しいポイントだということを覚えておいてください。
画面を戻したい場合はスワイプで戻ります。
メイン画面の ListView を作る
では画面遷移のようなものができたので、メイン画面をリストにしてみましょう。
ポケモンはとんでもない数(id=1010)いますので(執筆時)単純なリストを作るとちょっとまずいです。まずさも含めてみていきましょう。
ということで、さっきのボタンを Widget として別のクラスにして、1010 種類のポケモンそれぞれのために並べてみましょう!!
class TopPage extends StatelessWidget {
const TopPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: const [
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
PikaButton(),
],
),
);
}
}
class PikaButton extends StatelessWidget {
const PikaButton({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return ElevatedButton(
child: const Text('pikachu'),
onPressed: () => {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => const PokeDetail(),
),
),
},
);
}
}
いや、これは面倒だ。
ふふふ、馬鹿みたいに並べちゃって・・・
こういうときは Iterator っていうのを使うんだぜ!
class TopPage extends StatelessWidget {
const TopPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: List.generate(10000, (id) => id)
.map((val) => PikaButton(index: val))
.toList(),
),
);
}
}
とはいえ、本来はこちらのほうがベターです。
class TopPage extends StatelessWidget {
const TopPage({Key? key}) : super(key: key);
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: 10000,
itemBuilder: (context, index) => PikaButton(index: index)),
);
}
}
ListView についてはこちらの動画でパフォーマンスのことについて説明されています。
平たく言えば、レンダリングを必要最小限にすべきということです。
では、リストから選択すると詳細に行くんだよ、ということがわかるようにしてみましょう。
リストは ListTile で作ると簡単です。
class PokeListItem extends StatelessWidget {
const PokeListItem({Key? key, required this.index}) : super(key: key);
final int index;
Widget build(BuildContext context) {
return ListTile(
leading: Image.network(
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
height: 50,
width: 50,
),
title: const Text('pikachu'),
onTap: () => {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => const PokeDetail(),
),
),
},
);
}
}
この PokeListItem を ListView.builder で回せばいい感じです。
それっぽくはなりましたが、ちょっと見た目をもう少しそれっぽくしたいので、調整します。
class PokeListItem extends StatelessWidget {
const PokeListItem({Key? key, required this.index}) : super(key: key);
final int index;
Widget build(BuildContext context) {
return ListTile(
leading: Container(
width: 80,
decoration: BoxDecoration(
color: Colors.yellow.withOpacity(.5),
borderRadius: BorderRadius.circular(10),
image: const DecorationImage(
fit: BoxFit.fitWidth,
image: NetworkImage(
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
),
),
),
),
title: const Text(
'Pikachu',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
subtitle: const Text(
'⚡️electric',
),
trailing: const Icon(Icons.navigate_next),
onTap: () => {
Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => const PokeDetail(),
),
),
},
);
}
}
LitTile は leading, title, subtitle, trailing などのスマホあるあるな固定位置に Widget を配置できるのでいい感じですね。
PokeListItem が随分と大きくなってしまったので、別のファイルに取り出しておきましょう。
これにて Step3 は終了です。