【Flutter】比率によるレイアウトを学ぶ

## 初めに

Flutterで比率によるレイアウトをするにあたって躓いた部分を記事にしたいと思います。
今回は、`Column` `Row`のプロパティでレイアウトする方法と、`Flexible`Widget系の`flex`を指定してレイアウトする方法をご紹介します。
そんなの余裕だよ！って方は、飛ばして実践編に取り組んでみてください！

## Tips
レイアウトを考えるときには、[Dart DevTools](https://dart.dev/tools/dart-devtools) の [Flutter inspector](https://docs.flutter.dev/development/tools/devtools/inspector) が超便利です！
Tree構造や、余白、Widgetの範囲まで可視化してくれます！

|Android Studio|
|:---:|
|<img src = 'https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/298cc8a0-7de2-6469-c83b-98706896be90.png'>|


|Visual Studio Code|
|:---:|
|<img src = 'https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/1e9facc7-1db2-b5ea-cc40-fe48e2b54ca3.png'>|

:::note warn
以下で画面に青線や、黄色線が表示されていますが、Flutter Inspectorによるものです。
不具合により、Flutter Inspectorを使えなかったり、上記のステップを飛ばした方はご注意ください。
:::


:::note info
今回は、比率をわかりやすくしたいので、`Row`ではなく`Column`でサンプルを作っていきます。（この記事で伝えたいことは、`Row`にもそのまま適用できます）
:::
:::note info
全ての`Column`は`Center`で囲っています。
:::

## 復習編

### 初めは、シンプルにColumnで囲う

当たり前ですが、上から順番に詰めていますね。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/7675c9fb-91ba-0bf8-02b8-2ed10c28e707.png" width='300'>

### mainAxisAlignmentプロパティを指定
childrenの要素数に応じて、Widget間の余白を計算してくれます！

|spaceAround|spaceBetween|spaceEvenly|
|:---:|:---:|:---:|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/ada11d85-0252-690f-8bb1-f48a6938fc88.png">|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/1450e53d-1d33-bacd-e57f-fb164f33527f.png">|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/fb1b3ffc-209f-95d4-6cb0-ccea5f264011.png">|

### Expandedで囲う
[Expanded](https://api.flutter.dev/flutter/widgets/Expanded-class.html)は、子要素を描画領域の最大サイズまで引き伸ばす、もしくは縮小させる Widget です。

ただし、`Expanded`で囲っただけでは、Buttonのタップ可能範囲も引き伸ばされてしまうので、`Center`で囲ってあげましょう。
これで、`Column`に`mainAxisAlignment.spaceAround`を指定した場合と同じレイアウトになりましたね。

|Expanded -> child|Expanded -> Center -> child|
|:---:|:---:|
|<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/c0e0a19f-156a-3d71-3ada-4f0418b2ba44.png" width='300'>|<img width ='300' src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/e2eb9966-7540-5fd2-697d-f1979cef97bc.png">|

## 実践編
それでは、以下の仕様を満たしてレイアウトしてみましょう！

- `Icon(Icons.flutter_dash_outlined)`と`Text`の間は、50 logical pixel
- アイコンのサイズは100
- `Icon(Icons.flutter_dash_outlined)`と`Text`を合わせた矩形(以下`矩形Widget`)の垂直中央は画面の中央と同じ
- `Text`と`Icon(Icons.sentiment_satisfied)`の間は、100 logical pixel

<img width = '300' src ='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/4b8dfd76-c8b3-03f1-01d8-dc1a95ad81b1.png' > 

<details>
<summary>ヒント</summary>

[Spacer](https://api.flutter.dev/flutter/widgets/Spacer-class.html)と`Expanded`を組み合わせてみよう！


</details>

### 解答例

```dart
class ColumnWithExpandedAndSpacer extends StatelessWidget {
  const ColumnWithExpandedAndSpacer({super.key});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Spacer(
          // 1の場合は省略可能
          flex: 1,
        ),
        Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [
              Icon(
                Icons.flutter_dash_outlined,
                size: 100,
              ),
              SizedBox(
                height: 50,
              ),
              Text('I love Flutter'),
            ],
          ),
        ),
        Expanded(
          // 1の場合は省略可能
          flex: 1,
          child: Column(
            children: [
              const SizedBox(
                height: 100,
              ),
              Center(
                child: IconButton(
                  onPressed: () {
                    print('Tapped satisfied');
                  },
                  icon: const Icon(Icons.sentiment_satisfied),
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}
```

## 考え方

**`矩形Widget`を中心に設置する** ということを意識すぎると沼にハマってしまうかもしれません。実際に自分も詰まったことがあります。

少し見方を変えると、**`矩形Widget`の上下のスペースが同じであればよい** と考えることができそうですね！

そこで登場するのが、[Spacer](https://api.flutter.dev/flutter/widgets/Spacer-class.html)です！`Spacer`は`Expanded`と同じように、空いているスペースを描画領域の最大サイズまで引き伸ばしてくれます。
両方、`flex`というプロパティを保持しており、スペースの割合を指定することができます。
今回は明示的に`flex:1`としていますが、デフォルトで1が指定されるので省略可能です。

正確にいうと、`Expanded`が継承している`Flexible`でflexは設定されています。
<details>
<summary>Expanded</summary>

```dart
class Expanded extends Flexible {
  /// Creates a widget that expands a child of a [Row], [Column], or [Flex]
  /// so that the child fills the available space along the flex widget's
  /// main axis.
  const Expanded({
    super.key,
    super.flex,
    required super.child,
  }) : super(fit: FlexFit.tight);
}

class Flexible extends ParentDataWidget<FlexParentData> {
  /// Creates a widget that controls how a child of a [Row], [Column], or [Flex]
  /// flexes.
  const Flexible({
    super.key,
    this.flex = 1,
    this.fit = FlexFit.loose,
    required super.child,
  });
    :
    省略
    :
}


```

</details>
<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/e1205f49-a40f-86e9-8a0d-bd130e31526a.png' width = '300'>

試しに`Spacer`のflexに2を指定してみましょう。

```dart
Column(
  children: [
    const Spacer(
      // flex を指定！
      flex: 2,
    ),
    Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: const [
              Icon(
                Icons.flutter_dash_outlined,
                size: 100,
              ),
              SizedBox(
                height: 50,
              ),
              Text('I love Flutter'),
            ],
          ),
    Expanded(child: child),
  ],
),
```

<img src='https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2552610/3835df4a-cda4-49f2-774f-fe68ef28d78f.png' width = '300'>

`Spacer`　:　`Expanded` 　=　 2　:　1　になっていますね！！

## 最後に
今回は　比率によるレイアウトのサンプルをご紹介しました！
レスポンシブなデザインに対応するには、比率によるレイアウトを考えることは必須になってきます。

自分もまだまだ勉強中ですので、自由自在にデザインできるように皆さん一緒に頑張りましょう！





### 参考
[Flutter: MainAxisAlignmentを理解する](https://qiita.com/ikemura23/items/67af19b6cbf16fb0251a)
[Flexible と Expanded って何が違うんだっけ？](https://zenn.dev/pressedkonbu/articles/flexible-vs-expanded)