[Flutter] 独自のウィジェットを作成する(StatefulWidget)

Flutterアプリを制作中に何度も登場するウィジェットの組み合わせが出てくると思います。

それを共通利用できるウィジェットとして定義したうえで利用すると、buildメソッドの中が見通しくよくなり、また共通部品なのでメンテナンスも1ファイルだけで影響範囲が少なく済むという利点があります。

下記は私のNovel editorアプリの画面で、赤線で囲った各ブロックは一つの共通部品として定義されたものを使用して表示させています。

上記のウィジェットはStatefulWidgetを継承した独自クラスを作成し、その中にあるbuildメソッドで必要なウィジェットを定義して画面表示させています。

下記が上記ウィジェットのコード(抜粋)です。

class WidgetNovel extends StatefulWidget {
  final int novelId;
  :
  final VoidCallback onListPressedCallback;

  const WidgetNovel({
    required super.key,
    required this.novelId,
    :
    required this.onListPressedCallback,
  });

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

class WidgetNovelState extends State<WidgetNovel> {
  @override
  Widget build(BuildContext context) {
    :
    return Card(
      child: ListTile(
        /// カバー画像
        leading: Image(...),
        /// タイトル
        title: Text(...),
        /// キャッチコピー・状態・文字数等
        subtitle: Column(
          children: [
            Text(...),
            Container(
              child: Row(
                children: [
                  Chip(...),
                  Chip(...),
                  Chip(...),
                ],
              ),
            ),
          ],
        ),
        /// メニュー
        trailing: PopupMenuButton<int>(...),
        /// タップ時のイベント
        onTap: widget.onListPressedCallback,
      ),
    );
  }
}

詳細なコードは「:」や「…」で省いていますが、これで構造が分かると思います。

画面を作る時と同じようにStatefulWidgetクラスを継承したWidgetNovelクラスを定義し、そこに必要なパラメータを定義しています。上記の例だと「novelId」と「onListPressedCallback」の2つです。

実際に画面表示するウィジェットの処理はWidgetNovelのStateクラスを継承したWidgetNovelStateクラスのbuildメソッドで行われており、上記の例だとCardの中にあるListTileが冒頭にある画像の赤線で囲われた1ブロックごとに表示されていることが分かると思います。

また、この例ではListTile.onTapに渡すメソッドをウィジェットのパラメータとして定義しています。これによってこのウィジェットを使う側が挙動を変えられるようにしており、ある画面ではタップした時にその詳細を表示したり、ある画面ではタップした時に他の処理を行ったりできるようにしています。

標準で用意されているCardやListTileについても、AndroidStudio上でCtrl+Bしてその定義に飛ぶと分かるのですが、StatefulWidgetやStatelessWidgetを継承して作られています。

それらを見ることで独自のウィジェットの作り方のヒントになるものが山ほどあると思います。