CustomPainterは、Flutter SDKの抽象クラスであり、Canvas上でのカスタム2Dグラフィックスのレンダリングを完全に制御できます。開発者はCustomPainterのサブクラスを作成し、paint(Canvas, Size)とshouldRepaint(covariant CustomPainter oldDelegate)の2つの必須メソッドを実装します。Flutter APIドキュメント(2026年)によると、CustomPainterはCustomPaintウィジェットと共に使用され、アニメーションフレームや状態変更のたびに、渡されたCustomPainterオブジェクトにレンダリングを委任します。
主要ポイント
CustomPainterは、カスタム2Dグラフィックスを実装するためのFlutterの基底クラスです。AndroidでonDrawをオーバーライドするアプローチに代わり、より柔軟で宣言的なレンダリングモデルを提供します。開発者はCustomPainterのサブクラスを作成し、CanvasとSizeを受け取るpaintメソッドをオーバーライドして、Canvas上で全ての描画操作を実行します。CustomPaintウィジェットがpaintの呼び出しを管理し、結果をウィジェットツリーに統合します。
CustomPainterとCanvasを介した直接描画の主な違いは、レンダリングロジック(paint)と再描画トリガー(shouldRepaint)の分離です。Flutterシステムは、親ウィジェットが変更されるたびにshouldRepaintを呼び出します。shouldRepaintがfalseを返す場合、paintは呼び出されず、リソースを節約します。このメカニズムはRenderObjectのshouldRebuildと類似しており、3つのツリー(Widget、Element、RenderObject)を持つFlutterアーキテクチャの一部です。
CustomPaintは、painter(背景レイヤー)とforegroundPainter(前景レイヤー)のパラメータを受け入れるウィジェットです。両方のパラメータはCustomPainterオブジェクトを受け入れます。背景painterはCustomPaintの子ウィジェットの前に描画され、foregroundPainterはその後ろに描画されます。これにより、CustomPaintを装飾的な背景やオーバーレイを持つコンテナとして使用でき、通常のウィジェットを内部に埋め込む機能を失うことはありません。
Canvasは、Flutterにおける中心的な描画オブジェクトであり、グラフィックスサーフェスをカプセル化します。約40の描画メソッド(drawLine、drawCircle、drawRect、drawPath、drawArc、drawImage、drawPicture、drawPointsなど)を提供します。FlutterのCanvasはAndroid Canvasの同様のクラスに対応しますが、DartアーキテクチャとFlutter Engineに適合しています。Canvasは、原点(0、0)が左上隅となる座標系で動作します。
Paint — paint()メソッドと混同しないでください — は、描画スタイル(色、線の太さ、スタイル(fillまたはstroke)、不透明度、ブレンドモード、アンチエイリアシングマスク)を定義するクラスです。Paintは描画前に設定され、Canvasのメソッドに渡されます。重要:Paintは可変オブジェクトですが、一度作成して必要なパラメータのみを変更して再利用することをお勧めします。
| Canvasメソッド | 目的 | パラメータ |
|---|---|---|
| drawCircle | 円を描画 | Offset center, double radius, Paint paint |
| drawLine | 線分を描画 | Offset p1, Offset p2, Paint paint |
| drawRect | 長方形を描画 | Rect rect, Paint paint |
| drawPath | 任意のパスを描画 | Path path, Paint paint |
| drawArc | 円弧または扇形を描画 | Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint |
| drawRRect | 角丸長方形 | RRect rrect, Paint paint |
| drawOval | 楕円を描画 | Rect rect, Paint paint |
| drawImage | Imageから画像を描画 | Image image, Offset p, Paint paint |
Canvasは座標系の変換をサポートしています:translate(double dx, double dy)は原点を移動、rotate(double radians)はシステムを回転、scale(double sx, double sy)は拡大縮小、skew(double sx, double sy)は傾斜。変換の前にsave()でCanvasの状態を保存し、完了後にrestore()で復元することをお勧めします。これにより、後続のpaint呼び出しでの変換の蓄積を防ぎます。
paint(Canvas canvas, Size size)は、全ての描画ロジックを実装する必須メソッドです。canvasパラメータは描画のためのグラフィックスサーフェスです。sizeパラメータは描画に割り当てられた領域のサイズ(論理ピクセル単位の幅と高さ)です。Canvasは事前に設定されており、原点(0,0)が描画領域の左上隅に対応します。全ての座標はこの原点を基準に指定されます。
shouldRepaint(covariant CustomPainter oldDelegate)は、再描画の頻度を最適化する必須メソッドです。FlutterはCustomPainterに影響を与えるウィジェットツリーの更新のたびにshouldRepaintを呼び出します。メソッドがfalseを返す場合、既にレンダリングされた画像は有効と見なされ、paintは呼び出されません。典型的な実装は:return oldDelegate.someField != someField — 古いpainterと新しいpainterのフィールドを比較して再描画の必要性を判断します。
アニメーションや絶えず変化する画像(時計、読み込みインジケーター、グラフ)の場合、shouldRepaintは状態が変わるたびにtrueを返す必要があります。オブジェクトが静的(ロゴ、アイコン、背景)な場合、shouldRepaintは最初のレンダリング後にfalseを返します。Flutterは自動的にpaintの結果をキャッシュし、サイズ、不透明度、またはshouldRepaintの呼び出しが変更された場合にのみ再描画します。
GaugePainter — アニメーション塗りつぶし付きのカスタム円形ゲージを作成しましょう。CustomPainterは現在の値(0.0から1.0までの進行度)を受け入れ、グラデーション付きの円弧を描画します。shouldRepaintメソッドは最適化のために進行度の値を比較します。CustomPaintは描画領域を制限するためにsizeパラメータを指定してウィジェットツリーに配置されます。
class GaugePainter extends CustomPainter {
final double progress;
final Color startColor;
final Color endColor;
GaugePainter({
required this.progress,
this.startColor = Colors.blue,
this.endColor = Colors.cyan,
});
@override
void paint(Canvas canvas, Size size) {
final center = Offset(size.width / 2,
size.height / 2);
final radius = center.dx - 12;
final bgPaint = Paint()
..color = Colors.grey.withOpacity(0.3)
..style = PaintingStyle.stroke
..strokeWidth = 16
..strokeCap = StrokeCap.round;
final progressPaint = Paint()
..shader = LinearGradient(
colors: [startColor, endColor],
).createShader(
Rect.fromCircle(
center, radius))
..style = PaintingStyle.stroke
..strokeWidth = 16
..strokeCap = StrokeCap.round;
canvas.drawCircle(center, radius, bgPaint);
canvas.drawArc(
Rect.fromCircle(center, radius),
-math.pi / 2,
2 * math.pi * progress,
false, progressPaint);
}
@override
bool shouldRepaint(
GaugePainter oldDelegate) {
return oldDelegate.progress != progress;
}
}
この例では、Pathを使用して波形グラフを描画します。Pathを使用すると、線、ベジェ曲線、円弧から任意の輪郭を構築できます。輪郭を構築した後、色で塗りつぶすか、線で輪郭を描きます。この例では、波形をシミュレートする点の配列からグラフを構築しています。
class WavePainter extends CustomPainter {
final List<double> values;
WavePainter({required this.values});
@override
void paint(Canvas canvas, Size size) {
final path = Path();
final dx = size.width /
(values.length - 1);
path.moveTo(0, size.height);
for (var i = 0; i < values.length; i++) {
final x = i * dx;
final y = size.height *
(1 - values[i]);
path.lineTo(x, y);
}
path.lineTo(size.width, size.height);
path.close();
final fillPaint = Paint()
..color = Colors.blue.withOpacity(0.3)
..style = PaintingStyle.fill;
canvas.drawPath(path, fillPaint);
}
@override
bool shouldRepaint(
WavePainter oldDelegate) {
return oldDelegate.values != values;
}
}
painter(背景)はCustomPaintの子ウィジェットの前に描画されます。CustomPaintに子ウィジェット(childパラメータ)がある場合、背景painterはその下に表示されます。これは装飾に適しています:プログレスバー、背景パターン、透かし。foregroundPainterは子ウィジェットの後に描画され、つまりその上に表示されます。オーバーレイ(ハイライト、マスク、測定定規、注釈)に使用されます。
painterとforegroundPainterの違いはZオーダーにとって重要です。コンテンツの後ろに何かを描画する必要がある場合 — painterを使用します。コンテンツの上に描画する場合 — foregroundPainterを使用します。場合によっては、両方のpainterを同時に使用できます:painterを介した背景、foregroundPainterを介したオーバーレイラベル。これにより、異なるグラフィックレイヤーのロジックを2つの別々のCustomPainterクラスに分離できます。
paintメソッドのsizeパラメータは両方のpainterで同じであり、CustomPaintに割り当てられた領域のサイズによって決まります。サイズはCustomPaintのsizeパラメータで明示的に設定することも、親コンテナを介して暗黙的に設定することもできます。サイズがSize.zeroの場合、paintは呼び出されません — Flutterはサイズゼロの領域のレンダリングをスキップします。この動作は、要素の出現をアニメーション化する際に考慮する必要があります。
CustomPainterのパフォーマンスの主なルールは、paintメソッド内でオブジェクトを作成しないことです。Canvas、Paint、Path、Rect、Offset、およびその他の描画オブジェクトは、CustomPainterのコンストラクタで作成するか、キャッシュする必要があります。paint内でオブジェクトを作成すると、毎フレームの割り当てが発生し、毎秒60フレームで頻繁なガベージコレクションとアニメーションのジャンク(jank)を引き起こします。
Pathのキャッシュは、繰り返し使用される複雑なパスのレンダリングを大幅に高速化します。フレーム間でパスが変わらない場合(背景グリッドなど)、Pathを一度作成して何度も再利用します。RepaintBoundaryは、上位のウィジェットツリーが変更されたときにCustomPainterの再描画を防ぐラッパーウィジェットです。CustomPainterが静的な場合、分離のためにRepaintBoundaryでラップします。
頻繁に再描画されるアニメーションCustomPainterの場合、アニメーション値が変更されるたびにtrueを返すshouldRepaintと共にTickerまたはAnimationControllerを使用します。不要なフレームごとに再描画するのではなく、shouldRepaintでアニメーションフィールドを比較するのが最適です。複雑なグラデーションのRectキャッシュ:グラデーションの色が変わらない場合、Paint.shaderを介してShaderをキャッシュできます。
よくある質問
CustomPainterは、Canvasに何をどのように描画するかを定義するコントラクトクラスです。Canvasは描画オブジェクトそのもので、メソッド(drawCircle、drawLineなど)を提供します。CustomPainterはpaintメソッドを介してCanvasを受け取り、その使用を制御します。1つのCanvasは異なる時間に異なるCustomPainterによって使用されることがあります。
いいえ、CustomPainterはCustomPaintなしでは意味がありません。CustomPaintは、CustomPainterのpaintメソッドを呼び出して結果を画面に表示するウィジェットです。CustomPaintがなければ、Canvasは作成されず、paintは実行されません。ただし、Canvasを使用して独自のRenderObjectを実装することは可能ですが、これはより低レベルのアプローチです。
コンストラクタパラメータを介してアニメーション値をCustomPainterに渡しながら、Ticker付きのAnimationControllerを使用します。値が変更されたら、CustomPaintを含むウィジェットでsetState()を呼び出し、shouldRepaintの呼び出しをトリガーします。shouldRepaintがtrueを返すと、システムは新しいアニメーションフレームのpaintを呼び出します。
原因はほぼ常にshouldRepaintがfalseを返していることです。oldDelegateのフィールドが新しい値と正しく比較されているか確認してください。可変オブジェクトを使用している場合、オブジェクトの参照が同じままであるため、shouldRepaintが変更を検出できない可能性があります。正しい比較には==または不変オブジェクトを使用してください。
はい、CustomPainterは複雑なリストでカスタム装飾をレンダリングするために使用されます。ただし、項目を並べ替える際に、ListView内のRepaintBoundaryが各項目のレンダリングをキャッシュすることに注意してください。CustomPainterは軽量で、ドラッグアニメーションを遅くしないようにpaintに重い操作を含めるべきではありません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。