ColumnとRowで、CrossAxisAlignmentより細かい単位で子要素の位置を指定するぞ～

## tl;dr

- `Align`を使うと大体いい感じになる
- `Align`がなぜか適用されないとき
    - `Column`なら`Column`を`IntrinsicWidth`で囲む
    - `Row`なら`Row`を`IntrinsicHeight`で囲む
 
# これはなに

flutterで画面を作るときによく使うウィジェット、`Column`, `Row`のプロパティに`crossAxisAlignment`というのがありますね
楽にchildrenの位置を指定できるのですが、たまに「子要素一個一個に対して別々に寄せる方向を指定したい」みたいな場合もあります。
これを解消するときの方法とハマりポイントを説明します。

## `Align`を使おう

例えば、以下のような画面を作りたいとします：
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281726/418add1d-93e5-7eba-f40c-ce7fe41c9b2f.png)

このような場合、以下のような感じで書くと実現できます。

```dart
...
Row(
          children: [
            Align(
              alignment: Alignment.topCenter,
              child: Container(
                width: 50,
                height: 50,
                color: Colors.amber,
                child: const Text('1'),
              ),
            ),
            Expanded(
              child: Container(
                height: double.infinity,
                color: Colors.blue,
                child: const Center(
                  child: Text('2'),
                ),
              ),
            ),
            Align(
              alignment: Alignment.bottomCenter,
              child: Container(
                width: 50,
                height: 50,
                color: Colors.amber,
                child: const Text('3'),
              ),
            ),
          ],
        )
...
```

まあ書いてあるのをそのままって感じですが、1と3の`Container`を`Align`で囲んで`alignment`を指定するだけですね。
非常にシンプルです。

## おや、Rowの様子が‥

さて、Alignの使い方を覚えたあなたのもとにデザイナーさんがやってきてこう言います

「次こんな感じで作って欲しいんですけど～」
「あ、2番の箱の高さは固定で300でお願いします！」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281726/81e06881-87b4-6989-d27b-32cd41ad913f.png)

「了解です！すぐ終わりますよ～」（Rowを画面の上に寄せたいからColumnでRowを囲って、あとはRowの子要素を`Align`で囲むだけでしょ　そーれ）

```dart
...
        Column(
          children: [
            Row(
              children: [
                Align(
                  alignment: Alignment.topCenter,
                  child: Container(
                    width: 50,
                    height: 50,
                    color: Colors.amber,
                    child: const Text('1'),
                  ),
                ),
                Expanded(
                  child: Container(
                    height: 300,
                    color: Colors.blue,
                    child: const Center(
                      child: Text('2'),
                    ),
                  ),
                ),
                Align(
                  alignment: Alignment.bottomCenter,
                  child: Container(
                    width: 50,
                    height: 50,
                    color: Colors.amber,
                    child: const Text('3'),
                  ),
                ),
              ],
            )
          ],
        ),
...
```

「...あれ、なんでAlign効かないのこれ」

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281726/6b5967e0-6ab9-1b41-80d5-1f5f52d9d39a.png)

と、こういう場面ではAlignを使ってもいい感じになりません。

## Intrinsic〇〇を使おう

`Align`がうまくいかないのは、Flutterの描画の仕組み的に仕方ないからです。
Flutterでは画面レイアウトを計算する際、親要素が子要素に、子要素が取れる最大・最小のサイズを渡します。すると、子要素は自身のサイズを計算し、その結果を親に返します。
これを再帰的に繰り返していくことでレイアウトを作成していきます。
このような手法を採用しているお陰で計算量は少なく済むようですが、代わりに制約も大きくなってしまっているらしいです。
上で紹介したようなシチュエーションで`Align`がうまくいかないのもこれが原因らしいです。

このようなときに使えるのが`IntrinsicHeight`、`IntrinsicWidth`というウィジェットになります。
言語能力がないのでうまく説明できませんが、これを使えばいい感じになります。やったね。

ということで、`IntrinsicHeight`を使って修正したプログラムが以下になります。

```dart
...
        Column(
          children: [
            IntrinsicHeight(
              child: Row(
                children: [
                  Align(
                    alignment: Alignment.topCenter,
                    child: Container(
                      width: 50,
                      height: 50,
                      color: Colors.amber,
                      child: const Text('1'),
                    ),
                  ),
                  Expanded(
                    child: Container(
                      height: 300,
                      color: Colors.blue,
                      child: const Center(
                        child: Text('2'),
                      ),
                    ),
                  ),
                  Align(
                    alignment: Alignment.bottomCenter,
                    child: Container(
                      width: 50,
                      height: 50,
                      color: Colors.amber,
                      child: const Text('3'),
                    ),
                  ),
                ],
              ),
            )
          ],
        ),
...
```

実際に動かしてみるとこんな感じ、ちゃんとデザイン通りになっていますね
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2281726/97c787e5-c575-bda3-0f1f-caaa39d73c83.png)

## まとめ

Flutterの画面描画の仕組みめんどくさいね