FlutterでGridView内のタイルの高さを変える方法

# 何の記事か
Flutterでグリッドレイアウトを実装するとき、[GridView](https://docs.flutter.io/flutter/widgets/GridView-class.html)を使います。しかし、デフォルトではGridViewのタイルの高さは、横幅と同じ大きさに設定されます。つまり、以下のように、タイルの形は正方形となります。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155589/c1339ff8-645c-b26c-efe8-1104f75d9b14.png)

タイルの高さを変えたいと思ったときにどうすればいいか困ったので、備忘録として残しておきます。以下がゴールイメージです。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/155589/6bca105e-996a-172c-32cb-626547cef51c.png)

# 前提
**今回はscrollDirectionをAxis.verticalとした前提で話を進めます。**つまり、MainAxisはスクロール方向（=縦方向）、CrossAxisはスクロールと垂直な方向（=横方向）です。

scrollDirectionをAxis.horizontalとした場合、MainAxisが横方向、CrossAxisが縦方向になり逆転するので、適宜読み替えてください。

# 実装方法
GridView.count、GridView.extentであれば、childAspectRatioを変更することで高さを変更できます。childAspectRatioは、MainAxisに対するCrossAxisの比率を表します。つまり、GridViewの場合は、**タイルの高さに対するタイルの横幅の比率**を表します。

```
childAspectRatio = タイルの横幅（Tile Width） / タイルの高さ（Tile Height）
```

※GridView、GridView.builder、GridView.customにはそのような設定が見つかりませんでした。（もしほかの方法があれば教えていただきたいです。） 

実装はシンプルです。

```dart
GridView.count(
  crossAxisCount: 2,
  mainAxisSpacing: 5,
  crossAxisSpacing: 5,
  childAspectRatio: 0.7, ←ここで設定している！
  children: <Widget>[
    Container(color: Colors.blueAccent,),
    Container(color: Colors.blueAccent,),
    Container(color: Colors.blueAccent,),
  ],
)
```

これだけでした。

# ソースコードの確認
GridView.countもGridView.extentもGridViewの名前付きコンストラクタです。設定されたchildAspectRatioがどこで使われているか気になったので、Flutterのソースコードを確認します。

まずは、GridViewのソースコードです。

```dart:scroll_view.dart
GridView.count({
  Key key,
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary,
  ScrollPhysics physics,
  bool shrinkWrap = false,
  EdgeInsetsGeometry padding,
  @required int crossAxisCount,
  double mainAxisSpacing = 0.0,
  double crossAxisSpacing = 0.0,
  ↓デフォルトで1.0に設定されています。タイルの幅と高さの比率なので、1.0ということは正方形になります。
  double childAspectRatio = 1.0,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  bool addSemanticIndexes = true,
  double cacheExtent,
  List<Widget> children = const <Widget>[],
  int semanticChildCount,
}) : gridDelegate = SliverGridDelegateWithFixedCrossAxisCount(
       crossAxisCount: crossAxisCount,
       mainAxisSpacing: mainAxisSpacing,
       crossAxisSpacing: crossAxisSpacing,
       ↓設定されたchildAspectRatioの値は、また別のクラスに渡されています。
       childAspectRatio: childAspectRatio,
     ),
     childrenDelegate = SliverChildListDelegate(
       children,
       addAutomaticKeepAlives: addAutomaticKeepAlives,
       addRepaintBoundaries: addRepaintBoundaries,
       addSemanticIndexes: addSemanticIndexes,
     ), super(
  key: key,
  scrollDirection: scrollDirection,
  reverse: reverse,
  controller: controller,
  primary: primary,
  physics: physics,
  shrinkWrap: shrinkWrap,
  padding: padding,
  cacheExtent: cacheExtent,
  semanticChildCount: semanticChildCount ?? children.length,
);
```

```dart:scroll_view.dart
GridView.extent({
  Key key,
  Axis scrollDirection = Axis.vertical,
  bool reverse = false,
  ScrollController controller,
  bool primary,
  ScrollPhysics physics,
  bool shrinkWrap = false,
  EdgeInsetsGeometry padding,
  @required double maxCrossAxisExtent,
  double mainAxisSpacing = 0.0,
  double crossAxisSpacing = 0.0,
  ↓こちらも同様にデフォルトで1.0に設定されています。
  double childAspectRatio = 1.0,
  bool addAutomaticKeepAlives = true,
  bool addRepaintBoundaries = true,
  bool addSemanticIndexes = true,
  List<Widget> children = const <Widget>[],
  int semanticChildCount,
}) : gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
       maxCrossAxisExtent: maxCrossAxisExtent,
       mainAxisSpacing: mainAxisSpacing,
       crossAxisSpacing: crossAxisSpacing,
       ↓設定されたchildAspectRatioの値は、また別のクラスに渡されています。
       childAspectRatio: childAspectRatio,
     ),
     childrenDelegate = SliverChildListDelegate(
       children,
       addAutomaticKeepAlives: addAutomaticKeepAlives,
       addRepaintBoundaries: addRepaintBoundaries,
       addSemanticIndexes: addSemanticIndexes,
     ), super(
  key: key,
  scrollDirection: scrollDirection,
  reverse: reverse,
  controller: controller,
  primary: primary,
  physics: physics,
  shrinkWrap: shrinkWrap,
  padding: padding,
  semanticChildCount: semanticChildCount ?? children.length,
);
```
次に、childAspectRatioを渡したSliverGridDelegateWithMaxCrossAxisExtentを確認します。
[SliverGridDelegateWithMaxCrossAxisExtent](https://docs.flutter.io/flutter/rendering/SliverGridDelegateWithMaxCrossAxisExtent-class.html)とは、タイルの大きさを含む、グリッドのレイアウトを構築するクラスです。そのクラスの[getLayout](https://docs.flutter.io/flutter/rendering/SliverGridDelegateWithMaxCrossAxisExtent/getLayout.html)メソッドでchildAspectRatioの値を使っていました。このメソッドは、タイルのサイズや位置を返すメソッドです。

```dart:sliver_grid.dart
class SliverGridDelegateWithMaxCrossAxisExtent extends SliverGridDelegate {

  ～その他のコードは省略～  

  @override
  SliverGridLayout getLayout(SliverConstraints constraints) {
    assert(_debugAssertIsValid());
    final int crossAxisCount = (constraints.crossAxisExtent / (maxCrossAxisExtent + crossAxisSpacing)).ceil();
    final double usableCrossAxisExtent = constraints.crossAxisExtent - crossAxisSpacing * (crossAxisCount - 1);
    final double childCrossAxisExtent = usableCrossAxisExtent / crossAxisCount;
    final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio; ←ここ!!!!!
    return SliverGridRegularTileLayout(
      crossAxisCount: crossAxisCount,
      mainAxisStride: childMainAxisExtent + mainAxisSpacing,
      crossAxisStride: childCrossAxisExtent + crossAxisSpacing,
      childMainAxisExtent: childMainAxisExtent,
      childCrossAxisExtent: childCrossAxisExtent,
      reverseCrossAxis: axisDirectionIsReversed(constraints.crossAxisDirection),
    );
  }
}
```

```dart
final double childMainAxisExtent = childCrossAxisExtent / childAspectRatio;
```
childCrossAxisExtentは、タイルの横幅を表します。
この箇所で、タイルの幅に対する高さを計算していました。

以上です。