Chapter 03

Step2: ポケモン紹介ページを作ろう

すぎっと@フルペラットエンジニア
すぎっと@フルペラットエンジニア
2021.12.07に更新

Step2 の概要

Step2 では、PokeAPI で提供される情報を手動で入力し、ポケモンをかわいく表示する画面を作成します。

本章で学べること

  • レイアウト系ウィジェットの使い方
  • 画像の扱い方

ポケモン紹介ページを作ろう

ポケモンの紹介ページを作ります。紹介ページではポケモンの情報をいい感じに見せることを目的としています。ポケモンの情報といってもさまざまです。名前、画像、タイプなどから、覚える技、ステータスの基礎値などさまざまです。

とりあえずここでは以下の項目を表示するページを作ってみようと思います。

  • ポケモンの画像
  • 名前
  • タイプ
  • 番号

それぞれの情報は PokeAPI から取得して使用します。最初はプログラムとして API 呼び出しをするのではなく、ブラウザ上でデータを確認し、直接データを指定して画面デザインの構築だけを行います。

https://pokeapi.co/

PokeAPI は RESTfulAPI です。ポケモンに関するさまざまな情報を取得することができます。API の使用には事前の登録も費用の支払いも一切が不要です。GitHub でオープンソースとして開発されています。

API って?RESTfulAPI って?については説明を省略します。

PokeAPI の使用のルールは以下のように記載されています。

# Fair Use Policy

PokéAPI is free and open to use. It is also very popular. Because of this, we ask every developer to abide by our fair use policy. People not complying with the fair use policy will have their IP address permanently banned.
PokéAPI is primarily an educational tool, and we will not tolerate denial of service attacks preventing people from learning.

## Rules:

- Locally cache resources whenever you request them.
- Be nice and friendly to your fellow PokéAPI developers.

平たくいえば節度ある使い方をしてねということです。
教育用としての使用が想定されており、ソースは BSD-3 でコマーシャル OK なので、この書籍の中で扱うのも大丈夫だろうと想定しています。(詳しい方、間違ってれば TwitterDM で教えてください 🙇🏻‍♂️

ピカチュウの情報を PokeAPI で取得する

PokeAPI のページではブラウザ上で API を試すことができます。今回の UI 構築にはポケモンの中で最も有名であろう、ピカチュウを使用します。

PokeAPI のページで以下の URL をリクエストしてみてください。

https://pokeapi.co/api/v2/pokemon/25

すると、JSON 形式で結果が得られます。

  • 名前は "name" にあるとおり "pikachu" で OK です。
  • タイプは "types" -> "0" -> "type" -> "name" にある "electric" です。電気タイプですね。

それぞれ、和名が欲しい場合は別途 API を呼び出す必要がありますが、取得可能です。

  • pokemon-species/25 をリクエストし、"names" -> "0" -> "name" で "ピカチュウ" が得られます
  • types/13 をリクエストし、"names" -> "0" -> "name" で "でんき" が得られます。

このアプリでは和名を得るために追加で API を叩くコストを払いたくなかったので、全て英名で作ります。

  • 画像は "sprites" -> "other" -> "official-artwork" -> "front_default" に URL があります
  • 番号は "id" で OK です

画像を配置する

では早速画像を配置していきます。HelloWorld のテキストの代わりに、Image.network()を使用します。

return Scaffold(
        body: Center(
          child: Image.network(
              "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png"),
        ),
      );

これでとりあえずはピカチュウが表示されます。フルサイズで表示されるのでちょっと主張激しめです。

ちょっとフルサイズは大きすぎる気がするので、もう少し小さくして見ます。

Image.network()には double heightdouble width というプロパティが用意されています。とりあえず適当にどちらも 300 にしてみます。

return Scaffold(
        body: Center(
          child: Image.network(
            "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
            height: 300,
            width: 300,
          ),
        ),
      );

先ほどよりはちょっと収まったような気がします。

しかし、固定のサイズにするとデバイスの差による見え方の違いが考慮できていません。レスポンシブデザインを意識するなら、もう少し考慮が必要です。Flutter はクロスプラットフォームな SDK ですので、レスポンシブデザインは意識していく必要がありますね。

Flutter におけるレスポンシブデザインについても別冊で触れようと思います。

次に名前とタイプと番号ですね。
名前は真ん中がいいですね。画像の真下に中央揃えで出しましょう。

return Scaffold(
        body: Center(
          child: Column(children: [
            Image.network(
              "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
              height: 100,
              width: 100,
            ),
            const Text(
              'pikachu',
              style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
            ),
          ]),
        ),
      );

名前はちょっと大きめにして、太字にしました。とりあえず愚直に設定します。テーマを使おうとかそういう難しい話は後回しにしましょう。

あれ?なんか上によりましたね。
Column を入れると、Center の中に入れていたとしても、何もしなければ上寄せになります。
こうしてみましょう。

return Scaffold(
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,   // <== 追加
            children: [
              Image.network(
                "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
                height: 100,
                width: 100,
              ),
              const Text(
                'pikachu',
                style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
              ),
            ],
          ),
        ),
      );

無事真ん中に寄りましたね。MainAxisAlignment は Column が縦方向なので、メイン = 縦方向の Align(寄せ感)を真ん中にするというものです。左右については Center() で実現しています。
実は Column にはもうひとつ似た設定があります。CrossAxisAlignment といいます。これは CrossAxis(横方向)なのですが、Center とは意味が異なります。いくつか比較してみましょう。

  • MainAxisAlignment.center のみ

  • MainAxis & CrossAxis (どちらも Center)

何も変わっていないように見えますね。何が変わったのでしょう・・・。

ではちょっとヒントです。Column を SizedBox でラップし、SizedBox を画面いっぱいにします。

return Scaffold(
        body: SizedBox(
          width: MediaQuery.of(context).size.width,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Image.network(
                "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
                height: 100,
                width: 100,
              ),
              const Text(
                'pikachu',
                style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
              ),
            ],
          ),
        ),
      );

すると、

きっちり真ん中に来ました。
これは何が違うのかというと、Column というウィジェットの横幅は CrossAxis を設定したところで、画面の横幅いっぱいにはなりません。そのため、Children の幅が小さければ、中央には寄ってくれないのです。

イメージが掴みやすいように今度は Container でラップし、背景色をつけてみましょう。

return Scaffold(
        body: Container(
          color: Colors.blue[200],
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Image.network(
                "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
                height: 100,
                width: 100,
              ),
              const Text(
                'pikachu',
                style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
              ),
            ],
          ),
        ),
      );

これだと分かりやすいですね。
どれだけ CrossAxis 方向に指定したところで、この狭さでは何も変わりません。

理屈がわかったところで、とりあえずここでは Center + Main で実装します。

では次にポケモンのタイプですね。

タイプはせっかくなのでカラフルにしてみようと思います。

Chip ウィジェットがかわいくておすすめです。

https://api.flutter.dev/flutter/material/Chip-class.html
return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Image.network(
              "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
              height: 100,
              width: 100,
            ),
            const Text(
              'pikachu',
              style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold),
            ),
            const Chip(
              label: Text('electric'),
              backgroundColor: Colors.yellow,
            ),
          ],
        ),
      ),
    );

はい、いい感じにかわいいですね。素晴らしい。

今回は Chip ウィジェットを使いましたが、そういった便利なウィジェットに詳しくないと Flutter のアプリは作れないんじゃないかと感じる方も多いようです。これはあながち間違いではなく、ウィジェットに詳しいことは Flutter アプリを組む上で有利なのは間違いありません。

そんな方には Flutter Widget of the Week を視聴されることをお勧めします。

https://www.youtube.com/playlist?list=PLjxrf2q8roU23XGwz3Km7sQZFTdB996iG

Widget of the Week では、Flutter の便利なウィジェットが短い動画で紹介されます。流し見て、"こういうのがあるんだな"、というのを知っておくことで、アプリ開発がスムーズになることでしょう。

一方で、このデザインは Chip が必須かというとそうではありません。Container で作って見ましょう。

Container(
  child: const Text('electric'),
  padding: const EdgeInsets.all(8),
  decoration: BoxDecoration(
    color: Colors.yellow,
    borderRadius: BorderRadius.circular(20),
  ),
),

遜色なく作れましたね。
ということで、ある程度は自作できるもんなんだというのを理解していただいた上で、便利なものはどしどし使いましょう。実装も Chip のほうがスッキリしますしね。

最後にポケモンの番号ですね。
番号はポケモン画像の左上に入れようと思います。画像の左上となると、"画像" という要素と、番号の"テキスト" を重ねる必要があります。

"重ねる" というレイアウトを実現するためには Stack を使います。

https://api.flutter.dev/flutter/widgets/Stack-class.html

では画像に重ねて見ましょう。

Stack(
  children: [
    Container(
      padding: const EdgeInsets.all(32),
      child: Image.network(
        "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png",
        height: 100,
        width: 100,
      ),
    ),
    Container(
      padding: const EdgeInsets.all(8),
      child: const Text(
        'No.25',
        style: TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.bold,
        ),
      ),
    ),
  ],
),

Stack は何もしなければ左上を基準に並べるので、こんな感じになります。

これにて Step2 はおしまいです。Widget を並べる方法について少し理解が進んだと思います。