🐥

【Flutter】CustomPainterで作るオリジナルBreadcrumb

2024/10/05に公開

はじめに

この記事はFlutterで CustomPainter を使った breadcrumb を実装した時のメモになります。
CustomPainterを使用することで、通常のWidgetでは難しいカスタム描画を行えます。

ゴール

以下の様な breadcrumb を描画する事をゴールとして進めてみたいと思います。

image1.png

Widgetを駆使してやってもできるかもしれませんが、今回はあくまで CustomPainter を使用して描画させる実装で進めていきます。

環境構築や準備

各バージョンや環境

MBA: M3 24GB macOS 14.6.1
Flutter: 3.24.3
VSCode: 1.94.0

実装

1. まずは単純な円を描画する

1-1. 土台として StatelessWidget クラスを継承したCustomPainterSample クラスを作成

この時点ではCustomPaintにpainterを指定せず、デフォルトの描画を確認します。

import 'package:flutter/material.dart';

class CustomPainterSample extends StatelessWidget {
  const CustomPainterSample({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Custom Painter Sample')),
      body: SizedBox(
        width: double.infinity,
        height: 120,
        child: Container(
          color: Colors.amber,
          padding: const EdgeInsets.all(5),
          child: CustomPaint(
            child: Container(
              color: Colors.red,
            ),
          ),
        ),
      ),
    );
  }
}

CustomPaintの Container がどんな風に描画されるのか分かりやすくする為に色を指定しました。

image2.png

1-2. CustomPainter を継承したPainterクラスを作成

import 'package:flutter/material.dart';

class CustomPainterSample extends StatelessWidget {
  const CustomPainterSample({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Custom Painter Sample')),
      body: SizedBox(
        width: double.infinity,
        height: 120,
        child: Container(
          color: Colors.amber,
          padding: const EdgeInsets.all(5),
          child: CustomPaint(
            painter: CirclePainter(), // 追加!
            child: Container(
              color: Colors.red.withOpacity(0.5), // 分かりやすく半透明にしました
            ),
          ),
        ),
      ),
    );
  }
}

class CirclePainter extends CustomPainter {
  
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint();
    // 半径10の円を描画
    paint.color = Colors.green;
    canvas.drawCircle(Offset(size.width / 2, size.height / 2), 10, paint);
  }

  
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

実行結果は以下の様になりました。

image3.png

CustomPaintchild に指定したWidgetが一番手前に来るみたいです。

また pain メソッドで渡ってくる size はCustomPaint(↑だと薄い赤色部分)のサイズが渡ってきます。

2. 均一に円を並べる

最後に breadcrumb っぽくなるように直線の上に、今回は固定数の円を並べる様な処理を実装してみたいと思います。

import 'package:flutter/material.dart';

class CustomPainterSample extends StatelessWidget {
  const CustomPainterSample({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Custom Painter Sample')),
      body: SizedBox(
        width: double.infinity,
        height: 120,
        child: Container(
          color: Colors.amber,
          padding: const EdgeInsets.all(5),
          child: CustomPaint(
            painter: CirclePainter(), // 追加!
            child: Container(
              color: Colors.red.withOpacity(0.5), // 分かりやすく半透明にしました
            ),
          ),
        ),
      ),
    );
  }
}

class CirclePainter extends CustomPainter {
  final double radius = 10;
  final double strokeWidth = 3;

  
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint();
    paint.color = Colors.blue;

    final double w = (size.width - (radius * 2)) / 6.0;
    paint.strokeWidth = strokeWidth;
    canvas.drawLine(
        Offset(0, size.height / 2), Offset(size.width, size.height / 2), paint);
    for (int i = 0; i < 7; i++) {
      drawOutlineCircle(canvas, Offset(w * i + radius, size.height / 2));
    }
  }

  // 円(塗りつぶし)
  void drawCircle(Canvas canvas, Offset c) {
    final Paint paint = Paint();
    paint.color = Colors.blue;
    canvas.drawCircle(c, radius, paint);
  }

  // 円(外線)
  void drawOutlineCircle(Canvas canvas, Offset c) {
    final Paint line = Paint()
      ..color = Colors.blue
      ..strokeCap = StrokeCap.round
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth;
    canvas.drawCircle(c, radius, line);

    final Paint paint = Paint();
    paint.color = Colors.white;
    canvas.drawCircle(c, radius - (strokeWidth / 2), paint);
  }

  
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

実行結果は以下の様になりました。
中央の水平線に沿って7つの円が均等に並んでいるのが確認できます。

image4.png

CustomPaintの Container を外すと👇の様になります。

image5.png

まとめ

あまり無いとは思いますが、CustomPainterを使って breadcrumb っぽいものを作る時の参考に少しでもなればと思います。

Discussion