flutterでm行n列のgridボタンが欲しくなったから作った

# 概要

ボタンをm行n列のgridで並べたくなったが，良い感じのコードが見当たらなかった．
生成系AI使ってもほしいものが手に入れられなかったので，なんとかして自作した．
需要がなんとなくあるかもしれないので共有する．

# コード

コード内のコメントは生成系AIに書かせた．

```dart
// Pairクラスは2つの異なる型の値を保持するためのジェネリックなクラス
class Pair<T, U> {
  final T first;  // 最初の要素
  final U second; // 2番目の要素

  // コンストラクタでfirstとsecondを設定
  const Pair(this.first, this.second);

  // Pairのクローンを作成するメソッド
  Pair<T, U> clone() {
    // 新しいPairインスタンスを返す
    return Pair<T, U>(first, second);
  }
}

// MatrixGridは、行と列のマトリックス形式でボタンを表示するウィジェット
class MatrixGrid extends StatelessWidget {
  final int numberOfRow;  // 行数
  final List<Pair<VoidCallback?, String>> pairOfFuncAndText;  // 各ボタンに対応する関数とテキストのペア

  // コンストラクタ。numberOfRowとpairOfFuncAndTextを受け取る
  const MatrixGrid({super.key, required this.numberOfRow, required this.pairOfFuncAndText});

  @override
  Widget build(BuildContext context) {
    // pairOfFuncAndTextをクローンして新しいリストを作成（参照型のデータの変更を防ぐため）
    List<Pair<VoidCallback?, String>> _pairOfFuncAndText = pairOfFuncAndText.map((pair) => pair.clone()).toList();

    // リストの要素数が行数で割り切れない場合、足りない分の空のペアを追加
    for (int i = 0; i < pairOfFuncAndText.length % numberOfRow; ++i) {
      _pairOfFuncAndText.add(Pair(null, ''));  // 空のボタン用のペア
    }

    // 列数の計算。行数で割り切れる列数を決定
    int numberOfColumn = _pairOfFuncAndText.length ~/ numberOfRow;

    List<Widget> vertical_list = [];  // 縦方向のWidgetのリスト（列のリスト）

    // numberOfColumn分、列を作成
    for (int i = 0; i < numberOfColumn; ++i) {
      List<Widget> horizontal_list = [];  // 横方向のWidgetリスト（行のリスト）

      // 各列の行に対してボタンを作成
      for (int j = 0; j < numberOfRow; ++j) {
        int index = numberOfRow * i + j;  // 現在のボタンのインデックスを計算

        // ボタンを作成して横方向のリストに追加
        horizontal_list.add(
          Expanded(
            child: OutlinedButton(
              onPressed: _pairOfFuncAndText[index].first,  // ボタンが押されたときに実行される関数
              style: OutlinedButton.styleFrom(
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.zero,  // ボタンの角を四角に設定
                ),
                minimumSize: Size(double.infinity, double.infinity),  // ボタンを可能な限り広く
              ),
              child: Text(_pairOfFuncAndText[index].second),  // ボタンのテキスト
            ),
          ),
        );
      }

      // 横方向のボタンリストを縦方向のリストに追加
      vertical_list.add(
        Expanded(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,  // 横方向に中央寄せ
            children: horizontal_list,  // 横方向のボタンリスト
          ),
        ),
      );
    }

    // 最後に、縦方向のリストをColumnでラップして中央に配置
    return Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,  // 縦方向に中央寄せ
        children: vertical_list,  // 縦方向のボタンリスト
      ),
    );
  }
}
```

# 使用例

```MatricGrid```を使うのに必要なのは以下の2つ．
* ```numberOfRow``` : 横方向に何個表示するのか
* ```pairOfFuncAndText``` : ボタンを押したときの挙動を指定する関数と，ボタンに表示するテキストの```Pair```の```List```

以下，例コード．

```dart
@override
Widget build(BuildContext context) {
  // 横方向に何個表示するのか指定
  // 縦方向に表示する個数は自動で計算される
  const int numberOfRow = 2;

  // リストの長さはnumberOfRowで割り切れなくても良い
  // 勝手に無意味なボタンを追加してくれる
  final List<Pair<VoidCallback?, String>> pairOfFuncAndText = [Pair(null, 'ボタン1'), Pair(null, 'ボタン2'), Pair(null, 'ボタン3'), Pair(null, 'ボタン4'), Pair(null, 'ボタン5')];
  return MatrixGrid(numberOfRow: numberOfRow, pairOfFuncAndText: pairOfFuncAndText);
}
```

生成系AIをうまく使って改造すると良いと思う．