Flutter レイアウト

# 元記事

https://docs.flutter.dev/get-started/fundamentals/layout
意訳しています．

# はじめに

FlutterがUIツールキットを与えられたら，Flutterウィジェットを使ってレイアウトを作ることに時間を費やすだろう．
このセクションでは，いくつかの一般的なレイアウトウィジェットを使ってレイアウトの組み立て方を学ぶ．
Flutterがどのようにレイアウトを作るのか理解するために，Flutter DevToolsを使うだろう．
最後に，Flutterの最も一般的なレイアウトエラーの1つである，恐ろしい「unbounded constraints」エラーに遭遇し，デバッグするだろう．

# Flutterのレイアウトを理解する

Flutterレイアウト機構の中心部分はウィジェットである．
Flutterにおいて，たとえレイアウトモデルがウィジェットであったとしても，ほとんどすべてのものはウィジェットである．
Flutterアプリで見れる```Image```，```Icon```，```Text```はすべてウィジェットである．
見ることもできないものもウィジェットである．
例えば，```Rows```，```Columns```，```grids```は見えるウィジェットを配置し，制約し，整列させる．

複雑なウィジェットを構築するためにウィジェットを作ることによってレイアウトを作成する．
例えば，下の図は，3つのアイコンとそれぞれの下にあるラベル，そして対応するウィジェット・ツリーを示している．
<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/simple_row_column_widget_tree.png">

この例では，アイコンとラベルを含む3つの列の行がある．
すべてのレイアウトでは，複雑ではなく，これらのウィジェットを組み合わせたものである．

# 制約条件

Flutterの制約条件を理解することはレイアウトがFlutterでどのように動作するのか理解する重要なことである．

一般的な意味で，レイアウトはウィジェットのサイズとスクリーンにおける位置を参照する．
すべてのウィジェットのサイズと位置は親ウィジェットによって制約される．
そのウィジェットが欲しい任意のサイズになることはできず，スクリーンでどの位置にいるのか決めることができない．
その代わり，大きさと位置はウィジェットとその親ウィジェットとのやり取りから決定される．

最も単純な例では，レイアウトのやり取りは以下のようになる．
1. ウィジェットは親ウィジェットから制約を受け取る
1. 制約条件は最小幅，最大幅，最小高さ，最大高さの4つの組み合わせのみである
1. ウィジェットは制約条件を満たすように幅と高さを決定し，親ウィジェットに幅と高さを返す
1. 親ウィジェットは，ウィジェットのサイズとアライメントを確認し，ウィジェットの位置を設定する．整列は，Centerのような様々なウィジェットや，RowとColumnの整列プロパティを使用して，明示的に設定することができる

Flutterでは，レイアウトのやり取りは以下のように要約される．
1. サイズ条件を渡すと
1. サイズが返ってきて
1. 親ウィジェットが位置を設定する

# ボックスタイプ

Flutterでは，```RenderBox```オブジェクトを通してウィジェットが描かれる．
そのオブジェクトは制約条件のやりとりの仕方を決定する．

一般的に，3種類のボックスがある．
* できる限り大きくする．例えば，```Center```や```ListView```が使われたとき
* 子ウィジェットと同じ大きさにする．例えば，```Transform```や```Opacity```が使われたとき
* できる限り特定のサイズにする．例えば，```Image```や```Text```が使われたとき

例えば```Container```のような，いくつかのウィジェットはコンストラクタの引数や型によって異なる．
```Container```コンストラクタはできる限り大きくするようにデフォルト設定するが，もし幅を与えられたら，その幅を尊重し，その幅に収める．

```Row```や```Column```など他のウィジェットは与えられた条件によって異なる．
フレックスボックスと制約条件について以下の記事をもっと読んだ方がよい．
https://docs.flutter.dev/ui/layout/constraints

# 単一ウィジェットのレイアウト

Flutterで1つのウィジェットをレイアウトするには，```Text```や```Image```のような可視ウィジェットを，```Center```ウィジェットのような画面上の位置を変更できるウィジェットで包む．

```dart
Widget build(BuildContext context) {
  return Center(
    child: BorderedImage(),
  );
}
```

以下の図について，左側は整列されておらず，右側は中心に置かれている．
<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/center.png">

すべてのウィジェットは以下のどちらかのメンバ変数を持っている．
* ```Center```，```Container```，```Padding```など一つの子を持つ場合，```child```メンバ変数を持つ
* ```Row```，```Column```，```ListView```，```Stack```など複数の子を持つ場合，```children```メンバ変数を持つ

## Container

```Container```はレイアウト，ペイント，位置決め，サイジングのためにいくつかの便利なウィジェットを組み合わせたものである．
それはウィジェットにパディングとマージンを追加することもできる．
```Padding```ウィジェットを使って同じ効果を得ることができる．
以下は```Coontainer```の例である．
```dart
Widget build(BuildContext context) {
  return Container(
    padding: EdgeInsets.all(16.0),
    child: BorderedImage(),
  );
}
```

以下の図において，左側はパディングなしで，右側はパディングありである．
<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/padding.png">

Flutterでもっと複雑なレイアウトを作成するために，多くのウィジェットを組み合わせることができる．
例えば，```Container```と```Center```を組み合わせることができる．

```dart
Widget build(BuildContext context) {
  return Center(
    Container(
      padding: EdgeInsets.all(16.0),
      child: BorderedImage(),
    ),
  );
}
```

# 垂直や水平のいくつかのウィジェットのレイアウト

最も一般的なレイアウトパターンは垂直方向や水平方向にウィジェットを整列することである．
水平方向に整列するために```Row```ウィジェットを使うことができる．
垂直方向に整列するために```Column```ウィジェットを使うことができる．
このページの始めの画像を両方とも使っている．

これはもっとも単純な```Row```ウィジェットの例である．
```dart
Widget build(BuildContext context) {
  return Row(
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/row.png">

複雑なレイアウトを作成するために，```Row```と```Column```の子ウィジェットはさらに```Row```と```Column```を入れ子にすることができる．
例えば，```Column```を使うことでそれぞれの画像にラベルを付けることができる．
```dart
Widget build(BuildContext context) {
  return Row(
    children: [
      Column(
        children: [
          BorderedImage(),
          Text('Dash 1'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 2'),
        ],
      ),
      Column(
        children: [
          BorderedImage(),
          Text('Dash 3'),
        ],
      ),
    ],
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/nested_row_column.png">

## RowとColumnの中でウィジェットを整列する

以下の例では，それぞれのウィジェットは200ピクセルの幅があり，表示領域は700ピクセルある．
それぞれのウィジェットは左側から順に並び，余った領域は右側にある．
<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/left_alignment.png">

```mainAxisAlignment```と```crossAxisAlignment```のメンバ変数を使って，行または列の子プロパティの整列方法を制御する．
```Row```の場合，主軸は水平に，交差軸は垂直に走る．
```Column```の場合，主軸は垂直に，交差軸は水平に走る．
<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/axes_diagram.png">

主軸のアラインメントを```spaceEvenly```に設定することは，各画像の間，前後の自由な水平スペースを均等に分割する．

```dart
Widget build(BuildContext context) {
  return Row(
    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
    children: [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/space_evenly.png">

```Column```は```Row```と同時に動作する．
以下の例は3つの画像を縦に並べたものであり，画像の高さは100ピクセルである．
レンダーボックスの高さが300ピクセル以上あるため，```spaceEvenly```を設定することは，各画像の間，前後の自由な垂直スペースを均等に分割する．

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/col_space_evenly.png">

```mainAxisAlignment```と```crossAxisAlignment```のEnumはアラインメントのコントロールを行う．

## RowやColumnに収まるようにサイジングする

```Expanded```ウィジェットを使うことによって，```Row```や```Column```に収まるようにウィジェットのサイズを変える．
以前の例に```Expanded```ウィジェットを適用すると以下のようになる．

```dart
Widget build(BuildContext context) {
  return const Row(
    children: [
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
    ],
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/expanded_row.png">

```Expanded```ウィジェットは，兄弟ウィジェットに対してどれだけのスペースを取るかを指定することもできる．
例えば，その兄弟ウィジェットに対して2倍の大きさを占めるようなウィジェットを作ることを考える．
このためには，```Expanded```ウィジェットの```flex```メンバ変数を使う．
これに設定する整数値はウィジェットの```flex```係数を決定する．
デフォルト係数は1である．
以下は中央の画像の```flex```係数を2，他を1とした例である．

```dart
Widget build(BuildContext context) {
  return const Row(
    children: [
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        flex: 2,
        child: BorderedImage(width: 150, height: 150),
      ),
      Expanded(
        child: BorderedImage(width: 150, height: 150),
      ),
    ],
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/flex_2_row.png">

# DevToolsとデバッグレイアウト

場合によって，ボックスの制約条件は境界なしで無限になることがある．
これは，最大幅と最大高さが```double.infinity```に設定されている．
可能な限り大きくしようとするボックスは，束縛されない制約が与えられたときに有用に機能せず，デバッグモードでは例外を投げる．

レンダーボックスが非束縛制約で終わる最も一般的なケースは、フレックスボックス（```Row```または```Column```）内と、スクロール可能な領域（```ListView```や他の```ScrollView```サブクラスなど）内である．
例えば，```ListView```は交差軸で可能な限りスペースに合うように拡張しようとする．
もし，水平方向にスクロール可能な```ListView```の中に，垂直方向にスクロール可能な```ListView```があると，中の```ListView```は可能な限り水平方向に広がろうとする．
外側の```ListView```は水平方向にスクロール可能なため，無限の幅を持っている．

Flutterアプリをビルドする間に遭遇する一般的なエラーは，レイアウトウィジェットを正しく使わずに発生する非束縛制約に関するエラーである．

Flutterアプリを作り始めたときに直面する覚悟が必要なエラーが1つだけあるとすれば，それはこれだろう．
https://www.youtube.com/watch/jckqXR5CrPI

# スクロール可能なウィジェット

Flutterはスクロール可能な多くの内蔵ウィジェットを持っており，特定のスクロールする動作を作成するためのカスタマイズできる多くのウィジェットがある．
このページでは，スクロール可能なリストを作成するためのウィジェットと同様の，スクロール可能な一般的なウィジェットを作成する．

## ListView

```ListView```はコンテンツがレンダーボックスよりはみ出た場合，自動的にスクロール可能にする```Column```のようなウィジェットである．
```ListView```を使う最も基本的な方法は，```Column```や```Row```と同様に使うことである．
```Column```や```Row```と違って，```ListView```はその子ウィジェットが横軸の利用可能なスペースをすべて占有する必要がある．
例を以下に示す．
```dart
Widget build(BuildContext context) {
  return ListView(
    children: const [
      BorderedImage(),
      BorderedImage(),
      BorderedImage(),
    ],
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/basic_listview.png">

とても長いコンテンツを表示したいときに```ListView```を使う．
この場合，```ListView.builder```コンストラクタを使うのが一般的である．
```builder```コンストラクタは画面上で見ることのできる子ウィジェットの```build```メソッドを一度のみ呼び出す．

以下の例では，```ListView```はto-doアイテムを表示している．
このtodoアイテムはリポジトリからフェッチするため，todoの数は分からない．
```dart
final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete),
          ],
        ),
      );
    },
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/listview_builder.png">

# 適応的なレイアウト

Flutterはモバイル・タブレット・デスクトップ・ウェブアプリに使われるため，スクリーンサイズや入力デバイスのように環境が異なる中でアプリケーションを適合させる必要がある．
これは適合可能でレスポンシブなアプリを作るのに必要である．

適合可能なウィジェットを作るためによく使われるウィジェットは```LayoutBuilder```ウィジェットである．
```LayoutBuilder```ウィジェットはFlutterで```builder```パターンを使うウィジェットである．

## Builderパターン

Flutterでは，builderと名前のつくコンストラクタを見つけるだろう．
以下のリストがすべてではない．
* ListView.builder
* GridView.builder
* Builder
* LayoutBuilder
* FutureBuilder

これらの異なるBuilderは異なる問題を解決するために使われる．
例えば，```ListView.builder```コンストラクタは主にリスト内のアイテムを遅延レンダリングするために使われ，```Builder```ウィジェットは深いウィジェットコードで```BuildContext```にアクセスするのに使われる．

異なるユースケースであるにもかかわらず，これらのbuilderはどのような動作をするかで統一されている．
```Builder```ウィジェットと```builder```コンストラクタすべては```builder```と呼ばれる引数を持つ．
例えば，```ListView.builder```の場合，```itemBuilder```になる場合もある．
また，```builder```引数はコールバックも受付可能である．
このコールバックは**builder関数**と呼ばれる．
builder関数は親ウィジェットにデータを渡すウィジェットであり，子ウィジェットを作成し返すための引数を親ウィジェットが使う．
builder関数はいつも，少なくとも1つの引数，通常は2つの引数を渡す．

例えば，```LayoutBuilder```ウィジェットは画面サイズを基にレスポンシブなレイアウトを作成するために使われる．
builderコールバックの本体は，親から受け取った```BoxConstraints```と，ウィジェットの```BuildContext```が渡される．
この制約条件のため，利用可能なスペースを基に異なるウィジェットを返すことができる．
https://www.youtube.com/watch/IYDVcriKjsw

以下の例では，画面の幅が600ピクセルかどうかでウィジェットを変更する```LayoutWidget```となっている．

```dart
Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (BuildContext context, BoxConstraints constraints) {
      if (constraints.maxWidth <= 600) {
        return _MobileLayout();
      } else {
        return _DesktopLayout();
      }
    },
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/layout_builder.png">

一方で，```ListView.builder```コンストラクタの```itemBuilder```コールバックはbuild内容とintが渡される．
このコールバックはリスト内のすべての要素に対して一度だけ呼ばれる．
そして，int引数はリストのどの要素なのか指定する．
flutterがUIを作成するときに初めて```itemBuilder```コールバックが呼ばれ，intは0から順に渡される．

インデックスを基にした特殊な設定を行うことができる．
上記の```ListView.builder```コンストラクタを使用した例を示す．
```dart
final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete),
          ],
        ),
      );
    },
  );
}
```

このコード例は，ビルダーに渡されたインデックスを使用して，アイテムのリストから正しいToDoを取得し，ビルダーから返されたウィジェットにそのToDoのデータを表示する．

これを例示するために，以下のコード例は，背景の色を変えるようにしている．
```dart
final List<ToDo> items = Repository.fetchTodos();

Widget build(BuildContext context) {
  return ListView.builder(
    itemCount: items.length,
    itemBuilder: (context, idx) {
      var item = items[idx];
      return Container(
        color: idx % 2 == 0 ? Colors.lightBlue : Colors.transparent,
        padding: const EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(item.description),
            Text(item.isComplete),
          ],
        ),
      );
    },
  );
}
```

<img src="https://docs.flutter.dev/assets/images/docs/fwe/layout/alternating_list_items.png">

# 追加の項目

ここを見て．
https://docs.flutter.dev/get-started/fundamentals/layout#additional-resources

# APIリファレンス

ここを見て．
https://docs.flutter.dev/get-started/fundamentals/layout#api-reference