Flutterの公式ドキュメントを読んで理解する 〜Building layouts/Tutorial〜

# 今回読んだページ
https://flutter.dev/docs/development/ui/layout/tutorial

# Building layouts
>What you'll learn?
* Flutterのレイアウトの仕組みがどのように動くか
* どのようにWidgetを垂直or水平に配置するか
* どのようにFlutterレイアウトを構築するか

* 以下のようなアプリのレイアウトを構築できる
<img src="https://flutter.dev/assets/ui/layout/lakes-2e8707102ca4f56f44e40ce3703606e1600ac1574fe5544c0f2d96f966bed853.jpg" />

* このガイドでは、Flutterのレイアウトへのアプローチを説明するためにいったん立ち戻り、スクリーン上に1つのWidgetを配置する方法を示す
* 水平方向または垂直方向にWidgetを配置する方法を議論した後で、最も一般的なレイアウトWidgetがカバーされる

## Step 0: Create the app base code
* [環境をセットアップ](https://flutter.dev/docs/get-started/install)してから、以下を実行する
    * [基本的なHello WorldのFlutterアプリを作る](https://flutter.dev/docs/get-started/codelab#step-1-create-the-starter-flutter-app)
    * 以下のように`appBar`のタイトルとアプリのタイトルを変更する

```diff:main.dart
  @override            
  Widget build(BuildContext context) {            
    return MaterialApp(            
-     title: 'Welcome to Flutter',            
+     title: 'Flutter layout demo',            
      home: Scaffold(            
      appBar: AppBar(            
-       title: Text('Welcome to Flutter'),            
+       title: Text('Flutter layout demo'),            
      ),            
      body: Center(            
        child: Text('Hello World'), 
```

## Step: 1: Diagram the layout
* 最初のステップは、レイアウトを基本的な要素にブレイクダウンすることである
    * `Row`と`Column`を特定する
    * レイアウトがGridを含んでいるか？
    * 重なっている要素はあるか？
    * UIにタブが必要か？
    * Alignmentやpadding,borderが必要な領域に注意
* まずは、より大きい要素を特定する
* この例では、4つの要素(画像、2つの行、テキストのブロック)が`Column`に配置されている

<img src="https://flutter.dev/assets/ui/layout/lakes-column-elts-b1f1ec8c2cc0cc667f3935dd665a27b2d15171c8466dc5b893e98b88b265414c.png" />

* 次は各行のダイアグラム
* 1つ目の行（タイトルセクションと呼ぶ）は、3つの子（テキストの列、スターアイコン、数字）を持つ。
* 最初の子（テキストの列）は、2行のテキストを含む
* その列は、多くのスペースを使うので`Expanded`widgetでラップする必要がある

<img src="https://flutter.dev/assets/ui/layout/title-section-parts-91480d296e122c9cf2994439b82da0c43df795c1085ec6efb9a916da371248c5.png" />

* 2つ目の行（ボタンセクションと呼ぶ）も3つの子（それぞれアイコンとテキストを持つ列）を持つ。

<img src="https://flutter.dev/assets/ui/layout/button-section-diagram-3dac85a884b67876ce7b39e4f0bd43b93886c8f61d25055ad2d0971adb16907c.png" />

* 一度レイアウトがダイアグラム化されると、それを実装するにはボトムアップアプローチが最も簡単である
* 深くネストされたレイアウトコードによる視覚的な混乱を最小限にするために、実装のいくつかは変数や関数に入れる

## Step 2: Implement the title row
* まず、タイトルセクションの左側の列を作っていく
* `MyApp`クラスの`build()`メソッドの上部に以下のコードを追加する

```dart:main.dart(titleSection)
Widget titleSection = Container(
  padding: const EdgeInsets.all(32),
  child: Row(
    children: [
      Expanded(
        /* 1
        `Column`widgetを`Expanded`widgetの中に置くと、
         行の中の残りのフリースペースをすべて使うように列を伸ばすことができる
        */
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            /* 2
            最初のテキストの行を`Container`に入れることで、
            paddingを設定できるようになる。
            2番目のテキストはグレーで表示する。
            */
            Container(
              padding: const EdgeInsets.only(bottom: 8),
              child: Text(
                'Oeschinen Lake Campground',
                style: TextStyle(
                  fontWeight: FontWeight.bold,
                ),
              ),
            ),
            Text(
              'Kandersteg, Switzerland',
              style: TextStyle(
                color: Colors.grey[500],
              ),
            ),
          ],
        ),
      ),
      /* 3 
      タイトル行の最後の2つのアイテムは、赤色の星アイコンと、テキスト"41"である。
      行全体が`Container`の中にあり、各エッジに沿って32ｐｘのpaddingがある。
      （一番上で指定している`padding: const EdgeInsets.all(32)`のこと）
      */
      Icon(
        Icons.star,
        color: Colors.red[500],
      ),
      Text('41'),
    ],
  ),
);
```
タイトルセクションを以下のようにapp bodyに加える。

```diff:main.dart
return MaterialApp(            
  title: 'Flutter layout demo',            
  home: Scaffold(            
    appBar: AppBar(            
      title: Text('Flutter layout demo'),            
    ),            
-   body: Center(            
-   child: Text('Hello World'),            
+   body: Column(            
+     children: [            
+       titleSection,            
+     ],            
    ),            
  ),            
);
```
>Tip
* コードをアプリに貼り付けると、インデントが歪むことがある。それは、Flutter editorで[automatic reformatting support](https://flutter.dev/docs/development/tools/formatting)を使うことで直すことができる。
* より速い開発体験のため、[hot reload](https://flutter.dev/docs/development/tools/hot-reload)機能を試そう
* もし問題があれば、自分のコードを[lib/main.dart](https://github.com/flutter/website/tree/master/examples/layout/lakes/step2/lib/main.dart)と比較してみよう

## Step 3: Implement the button row
* ボタンセクションは、同じレイアウト（テキストの行の上にアイコン）を使った3つの列を含んでいる。
* この行の列は等間隔で、テキストとアイコンはプライマリーカラーで色付けられている
* 各列を構成するコードはだいたい同じであるため、プライベートのヘルパー関数`buildButtonColumn()`を作成する
* `buildButtonColumn()`は、color, `Icon`, `Text`を受け取り、与えられた色がついたWidgetを返す

```dart:main.dart(_buildButtonColumn)
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // ···
  }

  Column _buildButtonColumn(Color color, IconData icon, String label) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
        Container(
          margin: const EdgeInsets.only(top: 8),
          child: Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w400,
              color: color,
            ),
          ),
        ),
      ],
    );
  }
}
```
この関数は、アイコンを直接 列に追加する。
テキストは`Container`に入れられ、上部のみのmarginをつけて、アイコンとテキストを離している。

* 上記の関数を呼び出し、色やアイコン、テキストを指定することで、これらの列を含んだ行を作成する
* 各列の前後、間が等間隔になるように、`MainAxisAlignment.spaceEvenly`を使って列を揃える
* 以下のコードを、`build()`メソッド内の`titleSection`の宣言の直下に追加する。

```dart:main.dart(buttonSection)
Color color = Theme.of(context).primaryColor;

Widget buttonSection = Container(
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      _buildButtonColumn(color, Icons.call, 'CALL'),
      _buildButtonColumn(color, Icons.near_me, 'ROUTE'),
      _buildButtonColumn(color, Icons.share, 'SHARE'),
    ],
  ),
);
```
そして、ボタンセクションをbodyに追加する。

```diff:main.dart
  return MaterialApp(
    title: 'Flutter Layout Demo',
    home: Scaffold(
      appBar: AppBar(title: Text("Flutter Layout Demo")),
      body: Column(
        children: [
          titleSection,
+         buttonSection
        ]
      ),
    )
  );
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/a8a522b9-7748-fdd4-aff5-b08511d97a8d.png)

## Step 4: Implement the text section
* テキストセクションは変数として定義する。
* `Container`にテキストを入れ、各エッジに沿ってpaddingを追加する。
* `buttonSection`の宣言の下に、以下のコードを追加する。

```dart
Widget textSection = Container(
  padding: const EdgeInsets.all(32),
  child: Text(
    'Lake Oeschinen lies at the foot of the Blüemlisalp in the Bernese '
        'Alps. Situated 1,578 meters above sea level, it is one of the '
        'larger Alpine Lakes. A gondola ride from Kandersteg, followed by a '
        'half-hour walk through pastures and pine forest, leads you to the '
        'lake, which warms to 20 degrees Celsius in the summer. Activities '
        'enjoyed here include rowing, and riding the summer toboggan run.',
    softWrap: true,
  ),
);
```

`softwrap`をtrueに設定することで、テキストの行は単語の境界で折り返す前に列の幅を埋める
そして、テキストセクションをbodyに入れる

```diff:main.dart
  return MaterialApp(
    title: "Flutter layout demo",
    home: Scaffold(
      children: [
        titleSection,
        buttonSection,
+       textSection
      ]
    )
  )
```

## Step 5: Implement the image section
* 4つの列要素のうち3つは完成し、残すは画像のみとなった
* 画像ファイルをこのプロジェクトに追加する
    * `images/`ディレクトリをプロジェクトのトップに作る
    * [lake.jpg](https://raw.githubusercontent.com/flutter/website/master/examples/layout/lakes/step5/images/lake.jpg)を追加する。

>`wget`は動かないことに注意。

* `pubspce.yaml`ファイルを更新し、`assets`タグを追加する。
* これにより、画像ファイルが使えるようになる。

```diff:pubspec.yaml
  flutter:
    uses-material-design: true
+   assets:
+     - images/lake.jpg
```
>Tip
* `pubspec.yaml`は大文字と小文字を区別するため、上記のように`assets:`と画像のURLを指定する
* インデントに対してもセンシティブなので、適切なインデントを使う。

* 以下のように、コードから画像を参照できる。

```diff:main.dart
          body: Column(children: [
+           Image.asset(
+             "images/lake.jpg",
+             width: 600,
+             height: 240,
+             fit: BoxFit.cover,
+           ),
            titleSection,
            buttonSection,
            textSection
          ]),
```
* `BoxFit.cover`は、画像をできるだけ小さく、かつレンダーボックス全体をカバーするように、フレームワークに指示する

※ 下部に警告が出ているが、最後に修正される。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/1927e6aa-32d1-4520-1bde-85a920758bad.png)


## Step 6: Final touch
* 最後に、すべての要素を`Column`ではなく、`ListView`の中に並べる。
* なぜなら、アプリが小さいデバイスで実行されるとき、`ListView`はapp bodyのスクロールをサポートしているからである。

```diff:main.dart
    return MaterialApp(
        title: 'Flutter Layout Demo',
        home: Scaffold(
          appBar: AppBar(title: Text("Flutter Layout Demo")),
-         body: Column(children: [
+         body: ListView(children: [
            Image.asset(
              "images/lake.jpg",
```
>`Column`からスクロールをサポートしている`ListView`に変更することで警告が消える
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/526994/dfae0a34-ea18-e7db-a7e1-e4c6ac9b56fa.png)

**Dart code:** [main.dart](https://github.com/flutter/website/tree/master/examples/layout/lakes/step6/lib/main.dart)
**Image:** [images](https://github.com/flutter/website/tree/master/examples/layout/lakes/step6/images)
**Pubspec:** [pubspec.yaml](https://github.com/flutter/website/tree/master/examples/layout/lakes/step6/pubspec.yaml)

以上！
アプリをホットリロードすると、このページのスクリーンショットと同じようなレイアウトのアプリが見れるはず。

このレイアウトにインタラクティブ機能を追加するには、[Adding Interactivity to Your Flutter App](https://flutter.dev/docs/development/ui/interactive)を参照してね