maui-graphics-drawing について
.NET MAUI で GraphicsView を使った独自描画を行う開発者向けの実践スキルです。キャンバス状態、再描画、性能に加え、影やクリッピングの注意点を扱います。
主な機能
- 01キャンバス状態の保存と復元
- 02Invalidate() による再描画要求
- 03パスの事前計算と割り当て削減
- 04影とクリッピング状態の管理
- 05静的描画とアニメーション描画の選択
活用シーン
- 01GraphicsView の空白表示や状態漏れの調査
- 02アニメーションやレイヤー描画の実装
- 03フレームごとの描画性能の改善
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ davidortinau/ maui-skills --skill maui-graphics-drawingインストール後、Claude Code で/maui-graphics-drawingを入力して読み込まれたか確認します。
同梱ファイルreferences/graphics-drawing-api.md
詳細
.NET MAUI Graphics の描画
よくある落とし穴
| 問題 | 解決策 |
|---|---|
| 画面に何も描画されない | GraphicsView に Drawable が設定されていることと、コントロールの HeightRequest/WidthRequest が 0 以外であることを確認します |
| 図形間で状態が引き継がれる | 独立させる領域を SaveState()/RestoreState() のペアで囲みます |
| 影が後続の描画にも残る | 影を付けた要素の描画後に canvas.SetShadow(SizeF.Zero, 0, null) を呼び出します |
| クリッピングがリセットされない | クリッピングはフレーム内で累積します。クリップ領域を SaveState/RestoreState で囲みます |
| 描画中に UI がフリーズする | Draw() 内では I/O、ネットワーク処理、負荷の高い計算を行わないでください。UI スレッドで実行されます |
キャンバスの状態 — 必ず Save/Restore をペアにする
⚠️ SaveState/RestoreState のペアが揃っていないと、描画呼び出し間で状態が漏れます。
// ✅ Correct — isolated state
canvas.SaveState();
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 6;
canvas.DrawRectangle(10, 10, 80, 80);
canvas.RestoreState();
// Stroke reverts to previous values
// ❌ Wrong — state leaks to everything drawn after
canvas.StrokeColor = Colors.Red;
canvas.StrokeSize = 6;
canvas.DrawRectangle(10, 10, 80, 80);
// Every subsequent shape is now red with size 6
ストローク、塗りつぶし、フォント、影、クリップ、変換を保存/復元します。レイヤーごとに分離するには、呼び出しを入れ子にします。
再描画をトリガーする
// ✅ Correct — queue a redraw
graphicsView.Invalidate();
// ❌ Wrong — never call Draw() directly
myDrawable.Draw(canvas, rect);
Invalidate()は再描画をキューに追加します。フレームワークは次のフレームでIDrawable.Drawを呼び出します。- ⚠️
Invalidate()をタイトなループ内で呼び出すのは避けてください。状態変更をまとめてから、一度だけ無効化します。
パフォーマンスのヒント
Draw()を高速に保つ — パスやデータは描画メソッドの外で事前計算します。Draw()は毎フレーム呼び出されます。PathFオブジェクトを再利用する — 一度だけ作成してフィールドに保存し、繰り返し描画します。MeasureFirstItemのような考え方を使う — 同一の項目を多数描画する場合は、寸法を一度だけ計算します。- 割り当てを最小限に抑える — パスが変わらない場合は、
Draw()内でnew PathF()を使用しないでください。
// ✅ Pre-computed path (field)
private readonly PathF _starPath = BuildStarPath();
public void Draw(ICanvas canvas, RectF dirtyRect)
{
canvas.FillPath(_starPath);
}
// ❌ Allocating every frame
public void Draw(ICanvas canvas, RectF dirtyRect)
{
var path = new PathF();
// ...build path every frame...
canvas.FillPath(path);
}
影とクリッピング — 状態が残る場合の注意点
影は明示的に解除するまで、後続のすべての描画に適用されます。クリップは累積し、RestoreState() でのみ元に戻せます。
// ✅ Shadow: remove after use
canvas.SetShadow(new SizeF(5, 5), 4, Colors.Gray);
canvas.FillRectangle(20, 20, 100, 60);
canvas.SetShadow(SizeF.Zero, 0, null); // ← must remove
// ✅ Clip: isolate with SaveState/RestoreState
canvas.SaveState();
canvas.ClipRectangle(20, 20, 100, 100);
canvas.FillRectangle(0, 0, 200, 200); // clipped
canvas.RestoreState(); // clip removed
// ❌ Clip persists — everything after is also clipped
canvas.ClipRectangle(20, 20, 100, 100);
canvas.FillEllipse(150, 150, 50, 50); // unintentionally clipped!
描画呼び出しの前にプロパティを設定する
// ✅ Properties then draw
canvas.StrokeColor = Colors.Blue;
canvas.DrawRectangle(10, 10, 100, 50);
// ❌ Setting after draw has no effect on previous shape
canvas.DrawRectangle(10, 10, 100, 50);
canvas.StrokeColor = Colors.Blue;
判断の指針
| 必要なもの | 方法 |
|---|---|
| 単純な図形/静的なグラフィック | 単一の IDrawable を使い、Draw() で描画します |
| アニメーション付きのグラフィック | 外部で状態を更新し、タイマー/アニメーションから Invalidate() を呼び出します |
| 複雑なレイヤー構成のシーン | 複数の SaveState/RestoreState ブロック、または個別のドローアブルを使用します |
| 描画要素のヒットテスト | 図形の境界を手動で追跡します。GraphicsView には、描画コンテンツに対する組み込みのヒットテスト機能はありません |
| プラットフォーム固有のレンダリング | ハンドラー/プラットフォームコードを使用します。Microsoft.Maui.Graphics はクロスプラットフォーム専用です |
クイックチェックリスト
-
GraphicsViewにDrawableが設定され、サイズが 0 以外である -
Draw()が高速である — I/O や負荷の高い割り当てがない - 各
SaveState()に対応するRestoreState()がある - 影の使用後に
SetShadow(SizeF.Zero, 0, null)で解除している - クリップを
SaveState/RestoreStateブロックで囲んでいる - 適用する描画呼び出しの前にプロパティを設定している
-
Draw()を直接呼び出さず、Invalidate()を使用している
コメント
Markdown 対応