Skip to content

learn_flutter_layout

Uchijo edited this page Sep 7, 2022 · 4 revisions

画面の作り方

ここでは画面に部品をどうやってレイアウトしていくのかということを解説していきます。 このページは、公式の初心者向けページをベースに作っています。元のページを翻訳しながら読んだほうがいいかもしれないです。

最小限のアプリを作る

フラッターにおける最小限のアプリとは、 runApp() 関数に1つだけウィジェットを渡したものです。 前回作ったプロジェクトフォルダを開いて、libフォルダの中にある main.dart を全部消して以下のコードを写してからアプリを起動してみて下さい。

import 'package:flutter/material.dart';

void main() {
  runApp(
    const Center(
      child: Text(
        'Hello, world!',
        textDirection: TextDirection.ltr,
      ),
    ),
  );
}

非常に淡白ですが、画面にHello, worldと出力するだけのアプリができたかと思います。 これをどんどん複雑にしていくことで、みなさんがよく目にするようなアプリを作ることが可能となります。

ウィジェットとは

上の節でいきなり説明なしにウィジェットという単語が出てきて何かと思った方もいるかと思いますので、ここではウィジェットが何なのかを説明していこうと思います。

ウィジェットとは、画面を構成する要素のことを指します。そして、flutterではウィジェットを入れ子にしていくことで画面を作って行きます。

たとえば、LINEを開くとトークルーム一覧画面が出てくるかと思いますが、あの画面自体がFlutterではウィジェットです。 さらに、トークルームの名前を表示する文字列もウィジェット、その横にあるアイコン画像もウィジェットと、全てがウィジェットで構成されています。

Flutterから、「文字を表示するウィジェット(最初の例でいうと、Text( ... ) というやつ)」「複数のウィジェットを子供として縦に並べる親ウィジェット」「子のウィジェットを真ん中に配置するウィジェット(最初の例でいうと Center( ... ) というやつ)」「ボタンを表示するウィジェット」など、様々なウィジェットが提供されています。我々開発者は、このウィジェットを組み合わせて好みの画面を作るわけです。

上の最小限のアプリとして作ったものは、ウィジェットの入れ子は1回だけでアプリを完結させています。

ウィジェットを入れ子にしてみる。

では、実際にウィジェットを入れ子にして複雑な画面を作る前に、どのようなウィジェットの入れ子構造がありうるのかを説明します。

ウィジェットの入れ子構造を考える上で、大きく3種類のウィジェットがあるといえます。

  1. 子供となるウィジェットを持たない、末端となるウィジェット。文字を表示する Text() や、アイコンを表示する Icon() がこれにあたります。
  2. 子供となるウィジェットを1つだけ持つウィジェット。子ウィジェットを真ん中に表示する Center()などがこれにあたります。この種類のウィジェットに関しては、ウィジェットを生成する際に Center(child: ここに子供となるウィジェットを指定)のように child: を使うことで子供のウィジェットを指定できます。上の例の Center(child: Text(...))が良い例です。
  3. 子供となるウィジェットを複数個並列に持てるウィジェット。複数の子ウィジェットを受け取り、縦に並べる Column()などがこれにあたります。この種類のウィジェットは、ウィジェットを生成する際に Column(children: [Text('hoge'), Text('fuga')]) などとして、childrenを設定することで子供ウィジェットを指定できます。childにはウィジェットを渡していたのに対し、childrenにはウィジェットを格納した配列を渡すという点に注意してください。

これを踏まえて、ウィジェットを入れ子にして新たにアプリを作ってみましょう。

import 'package:flutter/material.dart';

void main() {
  runApp(
    Column(
      children: [
        Text('Hello, world!',
          textDirection: TextDirection.ltr,
        ),
        Text('Hello, world!',
          textDirection: TextDirection.ltr,
        ),
        Text('Hello, world!',
          textDirection: TextDirection.ltr,
        ),
      ],
    ),
  );
}

このアプリは、Columnが親ウィジェットで、子供として3つのTextウィジェットを渡しています。 Columnは子供として渡されたウィジェットを縦に並べるのでした。ですので、このアプリを実行すると、

Hello, world!
Hello, world!
Hello, world!

といったように、元のアプリでは1個しかなかったHello, world!を増殖させることができます。 なお、元のアプリにあったCenterがなくなってしまったので、これらの要素は真ん中には表示されなくなっていると思います。

慣れるために、時間がある人は演習としてHello, worldを10個表示するように変更してみましょう。

さて、今度は文字背景の色を変えてみましょう。そのためには、新たなウィジェットであるContainerを使用します。 これは、1つだけのウィジェットを子供とするウィジェットで、主にウィジェットに枠をつけたり、背景色を追加したり、幅や高さを指定したりしたい時に使用されるウィジェットです。

以下のようにコードを書き換えてみてください。

import 'package:flutter/material.dart';

void main() {
  runApp(
    Center(
      child: Container(
        color: Colors.blue, // ここで色を指定している
        child: Text(
          'Hello, world!',
          textDirection: TextDirection.ltr,
        ),
      ),
    ),
  );
}

恐らく、hello, world!の背景だけが青になるはずです。試しに Colors.blue のblue部分をredなど他の色に変えて遊んでみるとイメージが付きやすいでしょう。

ここまでできたら、最後におさらいとして、Hello, world!を画面に3個縦に並べて表示して、それぞれの背景色が赤、青、黄色となっているようなアプリを作ってみてください!

アプリらしい見た目にするおまじない

ここまで画面を作ってきたわけですが、あまりに我々が普段目にするアプリの画面とかけ離れていると感じたと思います。 もちろんすべて手動でColumnやContainer、Textを組み合わせてもよくあるアプリの画面を作ることは可能なのですが、果てしなく大変です。

これを楽にするために、FLutter側がいい感じのウィジェットを既に用意してくれていますので、今度はこれを使ってそれっぽい見た目のアプリを作ってみましょう。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('タイトルだよ'),
        ),
        body: Text('本体部分だよ'),
      ),
    ),
  );
}

新しく出てきたMaterialApp, Scaffold, AppBarというヤツらもウィジェットです。MaterialAppはおまじないで、Scaffoldは枠組み、AppBarは画面上のタイトルを表示するウィジェットと捉えてください。

Scaffoldには、appBar, bodyなど様々なウィジェットを設定することができます。Scaffoldをかませることで、ほとんど使っているウィジェットは先ほどと変わらないのにも関わらず、アプリの見た目が格段にいい感じになります。appBarには上のタイトルを表示させる場所に置くウィジェットを指定し、bodyには残った大部分の画面にどんなウィジェットを配置するかを指定できます。

これを改造して遊んでみると、さらにイメージが湧きやすくなっていいと思います。タイトル部分を書き換えたり、bodyに指定するウィジェットをTextからColumnにしてみるなど、色々できると思います。

ここで一つ、さらにレイアウトの可能性を広げるためのウィジェットをご紹介します。それは、Row()ウィジェットです。これはColumnの横バージョンです。Columnでは子供として渡した複数のウィジェットが縦に並べられたのに対し、Row()を使用するとこれが横に並べられるようになります。もちろんRowもウィジェットですので、Columnの中にRowを指定したり、その逆を行ったりもできます。

練習問題

ここで、練習問題として以下のような文字を表示する画面を作ってみてください。

ほげほげ
hogehogehoge
hogehogehoge
hogehogehoge

さっき試しに筆者が自分でやってみたら

        ほげほげ
hogehogehoge
hogehogehoge
hogehogehoge

みたいになったので、横のズレは気にしないで進めちゃって大丈夫です!

ただし、制約条件として、使用していいTextウィジェットは、 Text('hoge')と、Text(ほげほげ)のみとします。ColumnとRowをうまく組み合わせてこの画面を作れることを確認してください。

注意点として、上に書いた「アプリらしい見た目にするおまじない」を使用しないと変なエラーが出るっぽいです。しっかりMaterialAppScaffoldで囲んで書くようにしましょう

雛形(クリックで開ける)
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('練習問題'),
        ),

        /// bodyの中身を書き換えて、
        ///
        /// ほげほげ
        /// hogehogehoge
        /// hogehogehoge
        /// hogehogehoge
        ///
        /// が表示されるようにする。
        body: Text('aaaaaaa'),
      ),
    ),
  );
}
答えの例(クリックで開ける)
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('練習問題'),
        ),

        /// bodyの中身を書き換えて、
        ///
        /// ほげほげ
        /// hogehogehoge
        /// hogehogehoge
        /// hogehogehoge
        ///
        /// が表示されるようにする。
        body: Column(
          children: [
            Text('ほげほげ'),
            Row(
              children: [
                Text('hoge'),
                Text('hoge'),
                Text('hoge'),
              ],
            ),
            Row(
              children: [
                Text('hoge'),
                Text('hoge'),
                Text('hoge'),
              ],
            ),
            Row(
              children: [
                Text('hoge'),
                Text('hoge'),
                Text('hoge'),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

ウィジェットの見た目や挙動を変更する

先程Containerの背景色を変更したときに既に使ったテクニックですが、ウィジェットを呼び出す時、子供となるウィジェットを指定する以外にも値を渡してそのウィジェットの挙動や見た目を変更することができるものがあります。 Container(color: 色, child: 子供となるウィジェット)color: 色部分が最たる例です。

なお、ここの値は別に必ず入れないといけないわけではありませんし、逆に複数個指定することもできます(必ず入れないといけないものもありますが、そういうやつは大抵エディタが赤線を引いてくれます)。 ですので、理論上は Container(child: Text('hoge'))といったコードも何ら間違いではありません。あんまり書く意味はありませんが。

特におすすめなのが、Columnウィジェットの、mainAxisAlignment引数です。以下のコードを試しに動かしてみましょう。同じウィジェットでも全然違う動きをすることが分かると思います。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('あああ'),
        ),
        body: Column(
          /// ↓ここ1行を変更すると面白いと思う、多分。 spaceEvenlyの他に、spaceAround, end, startとかも設定できる。
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            Text('hello, world!'),
            Text('hello, world!'),
            Text('hello, world!'),
          ],
        ),
      ),
    ),
  );
}

これらの引数を指定することでウィジェットの振る舞いを変える方法はあまりにも多すぎてすべて紹介しきれないので、気になった人は自分で調べてみてください。 「flutter Container サイズ指定」であるとかで検索すると色々出てくると思います。

よく使われるウィジェット(どういうものなのかは自分で調べてみましょう)

  • Text()
  • Center()
  • Column()
  • Row()
  • SizedBox()
  • Padding()
  • ElevatedButton()
  • Icon()

また、flutter公式がyoutubeで出している widget of the week(今週のウィジェット) も中々おすすめです。

ウィジェットを自作する

ウィジェットは、flutterが提供している物を使う他に、自分で作ることも可能です。 よく使用する、再利用がしやすそうなパーツは自作ウィジェットとして保存してしまうと色々と楽になります。 以下は参考になりそうなページです。暇だったら目を通しておくといいでしょう。

https://blog.flutteruniv.com/flutter-widget-original/

ウィジェットの自作を行うための方法はいくつかありますが、ここでは一番簡単なStatelessWidgetというものを使用します。

Flutterのプラグインが入っているエディタで、 stlessと打ってみましょう。すると、下の候補に stless New Stateless Widgetみたいな感じの候補が出てくると思います。それが出たら、エンターキーを推してみましょう。

すると、以下のようなものが生えてくると思います。

import 'package:flutter/material.dart';

// 省略...

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

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

そしたらそのまま新しい自作ウィジェットに名前をつけましょう。恐らく勝手にカーソルが classextends の間に出てくると思うので、自分の好きな名前を入れてみましょう。

なお、この時の注意点として、自作ウィジェットの名前は大文字から始まる必要があります。このルールだけは守るようにしてください。(例:AaaaWidget

名前を入力し終わると、以下のような感じになると思います。ここでは MyWidget という名前を入力しました。

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

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

これで、自作ウィジェットの雛形を出すのは完了です。Flutterでアプリを作ることに例えると、flutter createが終わった感じです。

ここで生成される雛形は、いわゆる「何も出さないウィジェット」なので、実際に呼び出しても何も画面に描画されることはありません。しかし、上で Text() ウィジェットを呼び出したときと同じように、もうすでに MyWidget を呼び出すことは可能になっています。つまり、以下のように runAppに渡すウィジェットツリー内部で MyWidget を使用することができるのです。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('あああ'),
        ),
        /// さっき定義したMyWidgetが使えるようになっている。何もしないウィジェットなので何も表示されないことに注意。
        body: MyWidget(),
      ),
    ),
  );
}

/// 自作ウィジェットのMyWidgetの定義
class MyWidget extends StatelessWidget {
  const MyWidget({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

ここからこのウィジェットをカスタマイズして、自分好みのウィジェットが生成できるようにしていきます。といってもカスタマイズは簡単です。

以下に示すように、 build関数の中身を書き換えて、出したいウィジェットが戻り値としてリターンされるようにすればいいのです。

// 省略

  @override
  Widget build(BuildContext context) {
    // ↓ここで出したいウィジェットをreturnする。
    return Container();
  }

// 省略

ということで、まずはMyWidgetを「あああ」という文字を表示するだけのウィジェットに改造してみましょう。

上で説明した通り、自作ウィジェットを改造するためには build関数を書き換えて、その関数から戻る値を自分の好きなウィジェットにすれば良いのでした。 つまり、「あああ」という文字を表示するためだけのウィジェットを作るためには、さっきのMyWidgetbuild関数のreturnの後に続くウィジェットを、Containerから Text('あああ')に書き換えれば良いのです。

実際に書き換えたのが以下のコードブロックになります。

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('あああ'),
        ),
        /// ここでMyWidgetを呼び出しているので、動かすと画面にMyWidgetが表示されるようになっている。
        body: MyWidget(),
      ),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    ///   ↓ここをContainer()からText('あああ')に変更した。
    return Text('あああ');
  }
}

次はMyWidgetを ラーメンのアイコンの横に「ラーメン」という文字を表示するウィジェットに改造してみましょう。

ここでさっきの復習をしましょう。ウィジェットを横に並べるためには、 Row ウィジェットを使用し、その子ウィジェットに横並びにしたいウィジェットを渡せば良いのでした。 ということは、「ラーメンのアイコンを出すウィジェット」「ラーメンという文字を表示するウィジェット」の2つを Row に渡せば万事解決です。

ここまで読んだ人なら、文字を表示するウィジェットの出し方は分かると思いますが、アイコンの出し方はまだ知らないと思います。ということで以下がアイコンウィジェットの実際の使用例です。

/// ramen_diningを書き換えると別のアイコンが出せる。Icons.infoで出てくるやつとか見覚えあると思う。
Icon(Icons.ramen_dining)

ちなみに、ここで使えるアイコンは、Material Iconと呼ばれるものです。以下のページに載っているものは基本的に全部flutterで出すことができます。 https://fonts.google.com/icons?selected=Material+Icons

さて、ここまで来ればラーメンアイコンの横に「ラーメン」という文字列が表示されるウィジェットを作れるはずです。ということで、練習問題としてMyWidgetを実際に変更してみましょう。下に回答例も出しておきます。

練習問題

雛形(クリックで開けます)
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('あああ'),
        ),

        /// ここでMyWidgetを呼び出しているので、動かすと画面にMyWidgetが表示されるようになっている。
        body: MyWidget(),
      ),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    ///   ↓ここを書き換えて、ラーメンアイコンの横に「ラーメン」という文字列が表示されるウィジェットを作る。
    return Icon(Icons.info);
  }
}
練習問題回答例(クリックで開けます)
import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('あああ'),
        ),

        /// ここでMyWidgetを呼び出しているので、動かすと画面にMyWidgetが表示されるようになっている。
        body: MyWidget(),
      ),
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    ///   ↓ここを書き換えて、ラーメンアイコンの横に「ラーメン」という文字列が表示されるウィジェットを作る。
    return Row(
      children: [
        Icon(Icons.ramen_dining),
        Text('ラーメン'),
      ],
    );
  }
}

さて、これでMyWidgetを呼び出せば、「🍜ラーメン」を一発で呼び出せるようになりました。これが定義できたということは、大量に「🍜ラーメン」を出したい時にめちゃくちゃ楽になります。例えば、

🍜ラーメン
🍜ラーメン
🍜ラーメン
🍜ラーメン
🍜ラーメン
🍜ラーメン
🍜ラーメン

という画面を作りたいときは、

// 省略
  body: Column(
    children: [
      MyWidget(),
      MyWidget(),
      MyWidget(),
      MyWidget(),
      MyWidget(),
      MyWidget(),
      MyWidget(),
    ],
  ),
// 省略

だけで済むようになります。

さらに学習を進める

ここまでの話が理解できた人は、恐らくここに載せるページの内容がある程度分かると思います。全部読む必要はありません。チラ見して気になったやつだけ読んでみましょう。

https://qiita.com/ILTsubugai/items/b0a1ee1b9fbc00fe187a

https://qiita.com/shinbey221/items/a2af574622b8478b9cad

https://docs.flutter.dev/development/ui/layout

Clone this wiki locally