📐

FlutterでWidgetがMaterial Designガイドラインに沿って配置されているか確認する方法

2021/01/17に公開

はじめに

Flutterで開発していると、Material Components ( package:flutter/material.dart )を使うことが多いかと思います。
Components - Material Design

どうせなら、コンポーネント(以下、ウィジェット)の配置もMaterial Designのガイドライン(ウィジェットを8dp間隔のグリッドに揃える)に沿わせると、見た目綺麗で見やすくなるかと思います。
Spacing methods - Material Design

ですが、Flutter標準のデバッグ機能( Show debug paint)では、ウィジェットが「8dp間隔のグリッド」(以下、グリッド)に沿っているか確認することはできません。

何かいい方法はないかな?とつぶやいたところ、 monoさんから次のようなアドバイスをいただきました。

https://twitter.com/_mono/status/1346640587265462273

か、賢い!ということで、グリッドを描画する汎用Widgetを作ってみました。
なお、作ったウィジェットはパッケージ化してこちらで公開しています。
material_spacing_checker | Flutter Package

この記事では、グリッドを描画するWidgetを作る方法について解説します。

アプローチ

アプローチ的には次の手順を取ります。

  1. 画面のサイズを取得する
  2. 画面サイズを元に、グリッドを描画する場合、縦横何本ずつ線を引けば良いか計算する
  3. CustomPaintを使ってグリッドを描画する
  4. Stackを使って、グリッド(CustomPaint)と他Widgetを重ね合わせる

それでは、こちらを実装していきます。

実装方法

まず、画面のサイズは、次のように取得することができます。

final screenSize = MediaQuery.of(context).size;

このscreenSizeを元に、縦横に引くべき線の本数を計算します。

/// 8dpグリッドを描画するCustomPainter
class _GridPaint extends CustomPainter {
  _GridPaint({
     this.screenSize,
  });
  final Size screenSize;

  
  void paint(Canvas canvas, Size size) {
    final lineSpacing = 8;
    // 画面トップは4dp間隔を空けないと、グリッドがずれる
    final topMargin = 4;

    // +1しておかないと、線の数が足りないことがある
    final horizontalLineCount = screenSize.width ~/ lineSpacing + 1;
    final paint = Paint()
      ..color = Colors.red.withOpacity(0.7);

    // 水平方向に線を引く
    for (var i = 0; i < horizontalLineCount; i++) {
      canvas.drawLine(
        Offset(
          (lineSpacing * i).toDouble(),
          0,
        ),
        Offset(
          (lineSpacing * i).toDouble(),
          screenSize.height,
        ),
        paint,
      );
    }

    // 垂直方向に線を引く
    for (var i = 0; i < verticalLineCount; i++) {
      canvas.drawLine(
        Offset(
          0,
          (lineSpacing * i).toDouble() + topMargin,
        ),
        Offset(
          screenSize.width,
          (lineSpacing * i).toDouble() + topMargin,
        ),
        paint,
      );
    }
  }
}

あとは、Stackを使って重ね合わせれば、他Widget上にグリッドを描画できます。

Widget build(BuildContext context) {
    final screenSize = MediaQuery.of(context).size;
    return Stack(
      children: [
        child,// グリッドを重ね合わせたいWidget
        CustomPaint(
          painter: _GridPaint(
            screenSize: screenSize,
          ),
        )
      ],
    );
  }

実際に使ってみると、このような感じになります。
material spacing checker

参考文献

Spacing methods - Material Design

Discussion