CustomPainter ist eine abstrakte Klasse aus dem Flutter SDK, die Entwicklern die vollständige Kontrolle über das Rendern benutzerdefinierter 2D-Grafiken auf der Canvas gibt. Der Entwickler erstellt eine Unterklasse von CustomPainter und implementiert zwei obligatorische Methoden: paint(Canvas, Size) und shouldRepaint(covariant CustomPainter oldDelegate). Laut der Flutter API-Dokumentation (2026) wird CustomPainter zusammen mit dem CustomPaint-Widget verwendet, das das Rendern an das übergebene CustomPainter-Objekt bei jedem Animationsframe oder Zustandswechsel delegiert.
Wichtigste Punkte
CustomPainter ist eine Basisklasse von Flutter zur Implementierung benutzerdefinierter 2D-Grafiken. Sie ersetzt den Ansatz des Überschreibens von onDraw in Android und bietet ein flexibleres und deklaratives Rendering-Modell. Der Entwickler erstellt eine Unterklasse von CustomPainter, überschreibt die paint-Methode, in der er Canvas und Size erhält, und führt alle Zeichenoperationen auf der Canvas aus. Das CustomPaint-Widget verwaltet den paint-Aufruf und integriert das Ergebnis in den Widget-Baum.
Der Hauptunterschied zwischen CustomPainter und direktem Zeichnen über Canvas ist die Trennung von Rendering-Logik (paint) und Neuzeichnungs-Trigger (shouldRepaint). Das Flutter-System ruft shouldRepaint jedes Mal auf, wenn sich das übergeordnete Widget ändert. Wenn shouldRepaint false zurückgibt, wird paint nicht aufgerufen, was Ressourcen spart. Dieser Mechanismus ähnelt shouldRebuild in RenderObject und ist Teil der Flutter-Architektur mit ihren drei Bäumen (Widget, Element, RenderObject).
CustomPaint ist ein Widget, das die Parameter painter (Hintergrundebene) und foregroundPainter (Vordergrundebene) akzeptiert. Beide Parameter akzeptieren CustomPainter-Objekte. Der Hintergrund-painter wird vor dem untergeordneten Widget von CustomPaint gezeichnet, der foregroundPainter danach. Dies ermöglicht die Verwendung von CustomPaint als Container mit dekorativem Hintergrund oder Überlagerung, ohne die Möglichkeit zu verlieren, normale Widgets darin einzubetten.
Canvas ist das zentrale Zeichenobjekt in Flutter, das die grafische Oberfläche kapselt. Es stellt etwa 40 Zeichenmethoden bereit: drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints und andere. Canvas in Flutter entspricht einer ähnlichen Klasse in Android Canvas, ist jedoch an die Dart-Architektur und die Flutter Engine angepasst. Canvas arbeitet in einem Koordinatensystem, in dem der Ursprung (0, 0) die obere linke Ecke ist.
Paint — nicht zu verwechseln mit der paint()-Methode — ist eine Klasse, die den Zeichenstil definiert: Farbe (color), Linienstärke (strokeWidth), Stil (style: fill oder stroke), Deckkraft, Mischmodus (blendMode) und Antialiasing-Maske. Paint wird vor dem Zeichnen konfiguriert und an die Canvas-Methoden übergeben. Wichtig: Paint ist ein veränderbares Objekt, es wird jedoch empfohlen, es einmal zu erstellen und wiederzuverwenden, wobei nur die notwendigen Parameter geändert werden.
| Canvas-Methode | Zweck | Parameter |
|---|---|---|
| drawCircle | Kreis zeichnen | Offset center, double radius, Paint paint |
| drawLine | Liniensegment zeichnen | Offset p1, Offset p2, Paint paint |
| drawRect | Rechteck zeichnen | Rect rect, Paint paint |
| drawPath | Beliebigen Pfad zeichnen | Path path, Paint paint |
| drawArc | Bogen oder Sektor zeichnen | Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint |
| drawRRect | Rechteck mit abgerundeten Ecken | RRect rrect, Paint paint |
| drawOval | Oval zeichnen | Rect rect, Paint paint |
| drawImage | Bild aus Image zeichnen | Image image, Offset p, Paint paint |
Canvas unterstützt Transformationen des Koordinatensystems: translate(double dx, double dy) verschiebt den Ursprung; rotate(double radians) dreht das System; scale(double sx, double sy) skaliert; skew(double sx, double sy) neigt. Vor einer Transformation wird empfohlen, den Canvas-Zustand über save() zu speichern und nach Abschluss über restore() wiederherzustellen. Dies verhindert die Akkumulation von Transformationen bei nachfolgenden paint-Aufrufen.
paint(Canvas canvas, Size size) ist die obligatorische Methode, die die gesamte Zeichenlogik implementiert. Der Parameter canvas ist die grafische Oberfläche zum Zeichnen. Der Parameter size ist die Größe des zum Zeichnen zugewiesenen Bereichs (Breite und Höhe in logischen Pixeln). Canvas ist vorkonfiguriert, sodass der Ursprung (0,0) der oberen linken Ecke des Zeichenbereichs entspricht. Alle Koordinaten werden relativ zu diesem Ursprung angegeben.
shouldRepaint(covariant CustomPainter oldDelegate) ist eine obligatorische Methode, die die Neuzeichnungsfrequenz optimiert. Flutter ruft shouldRepaint bei jeder Aktualisierung des Widget-Baums auf, die CustomPainter betrifft. Wenn die Methode false zurückgibt, gilt das bereits gerenderte Bild als gültig und paint wird nicht aufgerufen. Eine typische Implementierung ist: return oldDelegate.someField != someField — Vergleich der Felder des alten und neuen painters, um die Notwendigkeit einer Neuzeichnung zu bestimmen.
Für animierte oder sich ständig ändernde Bilder (Uhren, Ladeanzeigen, Diagramme) sollte shouldRepaint bei jeder Zustandsänderung true zurückgeben. Wenn das Objekt statisch ist (Logo, Symbol, Hintergrund), gibt shouldRepaint nach der ersten Darstellung false zurück. Flutter speichert das paint-Ergebnis automatisch zwischen und zeichnet nur neu, wenn sich Größe, Deckkraft oder der shouldRepaint-Aufruf ändern.
Lassen Sie uns GaugePainter erstellen — eine benutzerdefinierte kreisförmige Anzeige mit animierter Füllung. CustomPainter akzeptiert den aktuellen Wert (Fortschritt von 0,0 bis 1,0) und zeichnet einen Bogen mit einem Farbverlauf. Die shouldRepaint-Methode vergleicht den Fortschrittswert zur Optimierung. CustomPaint wird mit dem size-Parameter in den Widget-Baum eingefügt, um den Zeichenbereich zu begrenzen.
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;
}
}
Das Beispiel zeichnet ein Wellendiagramm mit Path. Path ermöglicht das Erstellen beliebiger Konturen aus Linien, Bezier-Kurven und Bögen. Nach dem Erstellen der Kontur wird sie mit Farbe gefüllt oder mit einer Linie umrandet. Im Beispiel wird das Diagramm aus einer Reihe von Punkten erstellt, die eine Wellenform simulieren.
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 (Hintergrund) wird vor dem untergeordneten Widget von CustomPaint gezeichnet. Wenn CustomPaint ein untergeordnetes Widget (Parameter child) hat, wird der Hintergrund-painter darunter angezeigt. Dies ist geeignet für Dekorationen: Fortschrittsbalken, Hintergrundmuster, Wasserzeichen. foregroundPainter wird nach dem untergeordneten Widget gezeichnet, also darüber. Es wird für Überlagerungen verwendet: Hervorhebungen, Masken, Messlineale, Anmerkungen.
Der Unterschied zwischen painter und foregroundPainter ist entscheidend für die Z-Reihenfolge. Wenn Sie etwas hinter dem Inhalt zeichnen müssen — verwenden Sie painter. Wenn über dem Inhalt — foregroundPainter. In einigen Fällen können Sie beide painter gleichzeitig verwenden: Hintergrund über painter, Überlagerungsbeschriftung über foregroundPainter. Dies ermöglicht die Trennung der Logik verschiedener grafischer Ebenen in zwei separate CustomPainter-Klassen.
Der Parameter size in der paint-Methode ist für beide painter gleich und wird durch die Größe des für CustomPaint zugewiesenen Bereichs bestimmt. Die Größe kann explizit über den size-Parameter in CustomPaint oder implizit über den übergeordneten Container festgelegt werden. Wenn die Größe Size.zero ist, wird paint nicht aufgerufen — Flutter überspringt das Rendern für einen Bereich der Größe Null. Dieses Verhalten sollte beim Animieren des Erscheinens von Elementen berücksichtigt werden.
Die Hauptregel für die Leistung von CustomPainter ist, keine Objekte innerhalb der paint-Methode zu erstellen. Canvas, Paint, Path, Rect, Offset und andere Zeichenobjekte sollten im Konstruktor von CustomPainter erstellt oder zwischengespeichert werden. Das Erstellen von Objekten innerhalb von paint führt zu Allokationen bei jedem Frame, was bei 60 Bildern pro Sekunde häufige Garbage Collection und Animationsruckeln (Jank) verursacht.
Path-Zwischenspeicherung beschleunigt das Rendern wiederholter komplexer Pfade erheblich. Wenn sich der Pfad zwischen Frames nicht ändert (z. B. ein Hintergrundraster), erstellen Sie den Path einmal und verwenden Sie ihn mehrfach wieder. RepaintBoundary ist ein Wrapper-Widget, das das Neuzeichnen von CustomPainter verhindert, wenn sich der übergeordnete Widget-Baum ändert. Wenn CustomPainter statisch ist, umschließen Sie es zur Isolierung mit einem RepaintBoundary.
Für animierte CustomPainter mit häufigen Neuzeichnungen verwenden Sie Ticker oder AnimationController mit shouldRepaint, das bei jeder Änderung des animierten Werts true zurückgibt. Optimal ist es, das animierte Feld in shouldRepaint zu vergleichen, anstatt unnötig bei jedem Frame neu zu zeichnen. Rect-Zwischenspeicherung für komplexe Farbverläufe: Wenn sich die Farben des Verlaufs nicht ändern, können Sie den Shader über Paint.shader zwischenspeichern.
Häufig gestellte Fragen
CustomPainter ist eine Vertragsklasse, die definiert, was und wie auf Canvas gezeichnet wird. Canvas ist das Zeichenobjekt selbst, das Methoden bereitstellt (drawCircle, drawLine usw.). CustomPainter erhält Canvas über die paint-Methode und steuert dessen Verwendung. Eine Canvas kann zu verschiedenen Zeiten von verschiedenen CustomPainter-Objekten verwendet werden.
Nein, CustomPainter ergibt ohne CustomPaint keinen Sinn. CustomPaint ist das Widget, das die paint-Methode von CustomPainter aufruft und das Ergebnis auf dem Bildschirm anzeigt. Ohne CustomPaint wird Canvas nicht erstellt und paint nicht ausgeführt. Sie können jedoch ein eigenes RenderObject mit Canvas implementieren, aber dies ist ein Ansatz auf niedrigerer Ebene.
Verwenden Sie einen AnimationController mit einem Ticker, wobei Sie den animierten Wert über die Konstruktorparameter an CustomPainter übergeben. Wenn sich der Wert ändert, rufen Sie setState() in dem Widget auf, das CustomPaint enthält, was den shouldRepaint-Aufruf auslöst. Wenn shouldRepaint true zurückgibt, ruft das System paint für den neuen Animationsframe auf.
Die Ursache ist fast immer shouldRepaint, das false zurückgibt. Überprüfen Sie, ob die oldDelegate-Felder korrekt mit den neuen Werten verglichen werden. Wenn ein veränderbares Objekt verwendet wird, erkennt shouldRepaint möglicherweise keine Änderungen, da die Objektreferenz gleich bleibt. Verwenden Sie == oder unveränderliche Objekte für einen korrekten Vergleich.
Ja, CustomPainter wird zum Rendern benutzerdefinierter Dekorationen in komplexen Listen verwendet. Beachten Sie jedoch, dass beim Neuanordnen von Elementen der RepaintBoundary innerhalb der ListView das Rendern jedes Elements zwischenspeichert. CustomPainter sollte leicht sein und keine schweren Operationen in paint enthalten, um die Ziehanimation nicht zu verlangsamen.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch