GridViewで変な余白ができてレイアウトが崩れる

やりたいこと

GridViewに4つのバナー画像を画面幅いっぱいに2行2列で表示したい。

下記画像のような想定です。 f:id:fjswkun:20210723122646p:plain

プレースホルダーのバナー画像は下記のサイトを使って取得しています。 Placeholder.com: Placeholder.com – The Free Image Placeholder Service Favoured By Designers

問題

軽く実装してみたところバナーの上下に想定外の余白ができてしまいました。 f:id:fjswkun:20210723122436p:plain Imageウィジェットの高さの計算がうまくいってないようです。

実装

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'サンプルApp',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'サンプル'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const imageUrls = [
    'https://via.placeholder.com/350x80/0000FF?Text=Banner01',
    'https://via.placeholder.com/350x80/FF0000?Text=Banner02',
    'https://via.placeholder.com/350x80/FFFF00?Text=Banner03',
    'https://via.placeholder.com/350x80/000000?Text=Banner04',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Container(
          child: Container(
              child: GridView.count(
            crossAxisCount: 2,
            children: [for (final imageUrl in imageUrls) makeBanner(imageUrl)],
          )),
        ));
  }

  Widget makeBanner(String imageUrl) {
    return Image.network(imageUrl);
  }
}

対応

GridViewのchildAspectRatioはデフォルトで「1.0」が設定されていました。

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,
    double childAspectRatio = 1.0,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double? cacheExtent,
    List<Widget> children = const <Widget>[],
    int? semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.start,
    ScrollViewKeyboardDismissBehavior keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
    String? restorationId,
    Clip clipBehavior = Clip.hardEdge,
  })

バナーの幅と高さが同じになるようにウィジェットのサイズが計算されているようです。 対応方法としてはGridViewのchildAspectRatioにバナー画像のアスペクト比を設定したところ、想定していた通りの表示になりました。

f:id:fjswkun:20210723123638p:plain

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'サンプルApp',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'サンプル'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  static const imageUrls = [
    'https://via.placeholder.com/350x80/0000FF?Text=Banner01',
    'https://via.placeholder.com/350x80/FF0000?Text=Banner02',
    'https://via.placeholder.com/350x80/FFFF00?Text=Banner03',
    'https://via.placeholder.com/350x80/000000?Text=Banner04',
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text(widget.title),
        ),
        body: Container(
          child: Container(
              child: GridView.count(
            crossAxisCount: 2,
            childAspectRatio: 350/80,
            children: [for (final imageUrl in imageUrls) makeBanner(imageUrl)],
          )),
        ));
  }

  Widget makeBanner(String imageUrl) {
    return Image.network(
      imageUrl,
    );
  }
}