CustomPainter — is een abstracte klasse uit de Flutter SDK die ontwikkelaars volledige controle geeft over het tekenen van aangepaste 2D-graphics op Canvas. De ontwikkelaar maakt een subklasse van CustomPainter en implementeert twee verplichte methoden: paint(Canvas, Size) en shouldRepaint(covariant CustomPainter oldDelegate). Volgens Flutter API Documentation (2026) wordt CustomPainter gebruikt samen met de widget CustomPaint, die het tekenen delegeert aan het doorgegeven CustomPainter-object bij elk frame van een animatie of statuswijziging.
Belangrijkste punten
CustomPainter — is de basisklasse van Flutter voor het implementeren van willekeurige 2D-graphics. Het vervangt de benadering met het overschrijven van onDraw in Android en biedt een flexibeler en declaratiever tekenmodel. De ontwikkelaar maakt een subklasse van CustomPainter, overschrijft de methode paint, waar hij Canvas en Size ontvangt, en voert alle tekenbewerkingen uit op Canvas. De widget CustomPaint beheert de aanroep van paint en integreert het resultaat in de widgetboom.
Het belangrijkste verschil tussen CustomPainter en direct tekenen via Canvas — de scheiding van de tekenlogica (paint) en de trigger voor opnieuw tekenen (shouldRepaint). Het Flutter-systeem roept shouldRepaint aan elke keer dat de bovenliggende widget verandert. Als shouldRepaint false retourneert, wordt paint niet aangeroepen, wat resources bespaart. Dit mechanisme is analoog aan shouldRebuild in RenderObject en maakt deel uit van de Flutter-architectuur met haar drie bomen (Widget, Element, RenderObject).
CustomPaint — is een widget die de parameters painter (achtergrondlaag) en foregroundPainter (voorgrondlaag) accepteert. Beide parameters accepteren CustomPainter-objecten. De achtergrond-painter tekent vóór het kind van CustomPaint, en foregroundPainter erna. Dit maakt het mogelijk CustomPaint te gebruiken als een container met een decoratieve achtergrond of overlay, zonder de mogelijkheid te verliezen om gewone widgets erin te plaatsen.
Canvas — is het centrale object voor tekenen in Flutter, dat het grafische oppervlak inkapselt. Het biedt ongeveer 40 tekenmethoden: drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints en andere. Canvas in Flutter komt overeen met de analoge klasse in Android Canvas, maar is aangepast aan de architectuur van Dart en de Flutter Engine. Canvas werkt in een coördinatensysteem waar de oorsprong (0, 0) de linkerbovenhoek is.
Paint — niet te verwarren met de methode paint() — is een klasse die de tekenstijl definieert: kleur (color), lijndikte (strokeWidth), stijl (style: fill of stroke), transparantie, mengmodus (blendMode) en het antialiasingmasker. Paint wordt geconfigureerd vóór het tekenen en doorgegeven aan de methoden van Canvas. Belangrijk: Paint is een veranderlijk object, maar het wordt aanbevolen het eenmalig te maken en te hergebruiken, waarbij alleen de noodzakelijke parameters worden gewijzigd.
| Canvas-methode | Doel | Parameters |
|---|---|---|
| drawCircle | Cirkel tekenen | Offset center, double radius, Paint paint |
| drawLine | Lijnstuk tekenen | Offset p1, Offset p2, Paint paint |
| drawRect | Rechthoek tekenen | Rect rect, Paint paint |
| drawPath | Willekeurig contour tekenen | Path path, Paint paint |
| drawArc | Boog of sector tekenen | Rect rect, double startAngle, double sweepAngle, bool useCenter, Paint paint |
| drawRRect | Afgeronde rechthoek | RRect rrect, Paint paint |
| drawOval | Ovaal tekenen | Rect rect, Paint paint |
| drawImage | Afbeelding tekenen uit Image | Image image, Offset p, Paint paint |
Canvas ondersteunt transformaties van het coördinatensysteem: translate(double dx, double dy) verplaatst de oorsprong; rotate(double radians) roteert het systeem; scale(double sx, double sy) schaalt; skew(double sx, double sy) helt. Vóór een transformatie wordt aanbevolen de status van Canvas op te slaan via save() en na voltooiing te herstellen via restore(). Dit voorkomt ophoping van transformaties in volgende aanroepen van paint.
paint(Canvas canvas, Size size) — de verplichte methode waarin de gehele tekenlogica wordt geïmplementeerd. Parameter canvas — het grafische oppervlak om op te tekenen. Parameter size — de grootte van het toegewezen tekengebied (breedte en hoogte in logische pixels). Canvas is vooraf zo geconfigureerd dat de oorsprong (0,0) overeenkomt met de linkerbovenhoek van het tekengebied. Alle coördinaten worden ten opzichte van deze oorsprong vastgesteld.
shouldRepaint(covariant CustomPainter oldDelegate) — de verplichte methode die de frequentie van opnieuw tekenen optimaliseert. Flutter roept shouldRepaint aan bij elke update van de widgetboom die CustomPainter beïnvloedt. Als de methode false retourneert, wordt de reeds getekende afbeelding als geldig beschouwd en wordt paint niet aangeroepen. Typische implementatie: return oldDelegate.someField != someField — vergelijking van velden van de oude en nieuwe painter om de noodzaak van opnieuw tekenen te bepalen.
Voor geanimeerde of voortdurend veranderende afbeeldingen (klokken, voortgangsindicatoren, grafieken) moet shouldRepaint true retourneren bij elke statuswijziging. Als het object statisch is (logo, pictogram, achtergrond), retourneert shouldRepaint false na de eerste weergave. Flutter cached automatisch het resultaat van paint en tekent opnieuw alleen bij verandering van grootte, transparantie of aanroep van shouldRepaint.
Laten we GaugePainter maken — een aangepaste cirkelvormige indicator met geanimeerde vulling. CustomPainter ontvangt de huidige waarde (progress van 0.0 tot 1.0) en tekent een boog met een verloop. In de methode shouldRepaint wordt de progress-waarde vergeleken voor optimalisatie. CustomPaint wordt in de widgetboom geplaatst met de parameter size om het tekengebied te beperken.
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;
}
}
Het voorbeeld tekent een golfgrafiek met behulp van Path. Path maakt het mogelijk willekeurige contouren te bouwen uit lijnen, Bezier-curves en bogen. Na het bouwen wordt de contour gevuld met kleur of omlijnd met een lijn. In het voorbeeld wordt de grafiek gebouwd op basis van een reeks punten die een golfvorm nabootsen.
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 (achtergrond) tekent vóór het kind van CustomPaint. Als CustomPaint een kind heeft (parameter child), wordt de achtergrond-painter eronder weergegeven. Dit is geschikt voor decoratie: voortgangsbalken, achtergrondpatronen, watermerken. foregroundPainter tekent na het kind van CustomPaint, dus erboven. Het wordt gebruikt voor overlays: glanseffecten, masks, meetlinialen, annotaties.
Het verschil tussen painter en foregroundPainter is cruciaal voor de Z-order. Als je iets achter de inhoud wilt tekenen — gebruik painter. Als je boven de inhoud wilt tekenen — foregroundPainter. In sommige gevallen kun je beide painters tegelijk gebruiken: achtergrond via painter, label-overlay via foregroundPainter. Dit maakt het mogelijk de logica van verschillende grafische lagen te scheiden in twee aparte CustomPainter-klassen.
De parameter size in de methode paint is hetzelfde voor beide painters en wordt bepaald door de grootte van het aan CustomPaint toegewezen gebied. De grootte kan expliciet worden ingesteld via de size-parameter in CustomPaint of impliciet via de bovenliggende container. Als size gelijk is aan Size.zero, wordt paint niet aangeroepen — Flutter slaat het tekenen over voor een nulgebied. Dit gedrag moet in overweging worden genomen bij het geanimeerd verschijnen van een element.
De hoofdregel voor de prestaties van CustomPainter — maak geen objecten aan binnen de methode paint. Canvas, Paint, Path, Rect, Offset en andere tekenobjecten moeten worden aangemaakt in de constructor van CustomPainter of worden gecached. Het aanmaken van objecten binnen paint leidt tot allocaties op elk frame, wat bij 60 frames per seconde frequente garbage collection en schokkerige animaties (jank) veroorzaakt.
Het cachen van Path versnelt het tekenen van repetitieve complexe contouren aanzienlijk. Als het contour niet verandert tussen frames (bijvoorbeeld een achtergrondraster), maak dan Path eenmalig aan en gebruik het herhaaldelijk. RepaintBoundary — een wrapper-widget die voorkomt dat CustomPainter opnieuw wordt getekend bij verandering van de bovenliggende widgetboom. Als CustomPainter statisch is, wikkel het dan in RepaintBoundary voor isolatie.
Voor geanimeerde CustomPainter met frequent opnieuw tekenen, gebruik Ticker of AnimationController met shouldRepaint dat true retourneert bij verandering van de geanimeerde waarde. Optimaal is om het geanimeerde veld in shouldRepaint te vergelijken, niet om op elk frame zonder noodzaak opnieuw te tekenen. Rect-caching voor complexe verlopen: als het verloop geen kleuren verandert, kan men Shader cachen via Paint.shader.
Veelgestelde vragen
CustomPainter — is een contract-klasse die bepaalt wat en hoe er op Canvas getekend wordt. Canvas — het tekenobject zelf, dat methoden biedt (drawCircle, drawLine enz.). CustomPainter ontvangt Canvas via de methode paint en beheert het gebruik ervan. Eén Canvas kan op verschillende momenten door verschillende CustomPainter-objecten worden gebruikt.
Nee, CustomPainter heeft geen zin zonder CustomPaint. CustomPaint is de widget die de methode paint van CustomPainter aanroept en het resultaat op het scherm toont. Zonder CustomPaint wordt Canvas niet aangemaakt en wordt paint niet uitgevoerd. Men kan echter wel een eigen RenderObject met Canvas implementeren, maar dat is een lager niveau benadering.
Gebruik AnimationController met Ticker, geef de geanimeerde waarde door aan CustomPainter via de constructorparameters. Roep bij verandering van de waarde setState() aan in de widget die CustomPaint bevat, wat shouldRepaint triggert. Als shouldRepaint true retourneert, roept het systeem paint aan voor het nieuwe animatieframe.
De oorzaak is bijna altijd shouldRepaint dat false retourneert. Controleer of de velden van oldDelegate correct worden vergeleken met de nieuwe waarden. Als een veranderlijk object wordt gebruikt, kan shouldRepaint de wijzigingen mogelijk niet opmerken omdat de referentie naar het object hetzelfde blijft. Gebruik == of onveranderlijke objecten voor een correcte vergelijking.
Ja, CustomPainter wordt gebruikt voor het tekenen van aangepaste decoraties in complexe lijsten. Houd er echter rekening mee dat bij het herordenen van elementen RepaintBoundary binnen ListView de weergave van elk element cached. CustomPainter moet licht zijn en geen zware bewerkingen bevatten in paint om de sleepanimatie niet te vertragen.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook