CustomPainter — је апстрактна класа из Flutter SDK-а, која програмерима пружа потпуну контролу над исцртавањем прилагођене 2D графике на Canvas-у. Програмер креира подкласу CustomPainter-а и имплементира две обавезне методе: paint(Canvas, Size) и shouldRepaint(covariant CustomPainter oldDelegate). Према Flutter API Documentation (2026), CustomPainter се користи заједно са виџетом CustomPaint, који делегира исцртавање прослеђеном објекту CustomPainter-а на сваком кадру анимације или промене стања.
Главно
CustomPainter — је основна класа Flutter-а за имплементацију произвољне 2D графике. Она замењује приступ са преписивањем onDraw у Android-у, пружајући флексибилнији и декларативнији модел исцртавања. Програмер креира подкласу CustomPainter-а, преписује метод paint, где добија Canvas и Size, и извршава на Canvas-у све операције цртања. Виџет CustomPaint управља позивом paint-а и интегрише резултат у стабло виџета.
Кључна разлика између CustomPainter-а и директног цртања кроз Canvas — раздвајање логике исцртавања (paint) и окидача поновног исцртавања (shouldRepaint). Систем Flutter позива shouldRepaint сваки пут када се родитељски виџет промени. Ако shouldRepaint врати false, paint се не позива, што штеди ресурсе. Овај механизам је аналоган shouldRebuild-у у RenderObject-у и део је архитектуре Flutter-а са њена три стабла (Widget, Element, RenderObject).
CustomPaint — је виџет који прихвата параметре painter (позадински слој) и foregroundPainter (предњи слој). Оба параметра прихватају објекте CustomPainter-а. Позадински painter црта пре детета CustomPaint-а, а foregroundPainter после. То омогућава коришћење CustomPaint-а као контејнера са декоративном позадином или прекривачем, без губљења могућности уграђивања обичних виџета унутра.
Canvas — је централни објекат за цртање у Flutter-у, који инкапсулира графичку површину. Пружа око 40 метода за цртање: drawLine, drawCircle, drawRect, drawPath, drawArc, drawImage, drawPicture, drawPoints и друге. Canvas у Flutter-у одговара аналогној класи у Android Canvas-у, али је прилагођен архитектури Dart-а и Flutter Engine-а. Canvas ради у координатном систему где је почетак (0, 0) горњи леви угао.
Paint — не мешати са методом paint() — је класа која дефинише стил цртања: боју (color), дебљину линије (strokeWidth), стил (style: fill или stroke), провидност, режим мешања (blendMode) и маску за изглађивање. 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) нагиње. Пре трансформације препоручује се чување стања Canvas-а кроз save(), а након завршетка враћање кроз restore(). Ово спречава нагомилавање трансформација у наредним позивима paint-а.
paint(Canvas canvas, Size size) — обавезна метода у којој се имплементира целокупна логика цртања. Параметар canvas — графичка површина за цртање. Параметар size — величина области одређене за цртање (ширина и висина у логичким пикселима). Canvas је унапред подешен тако да почетак координата (0,0) одговара горњем левом углу области цртања. Све координате се задају у односу на овај почетак.
shouldRepaint(covariant CustomPainter oldDelegate) — обавезна метода која оптимизује учесталост поновног исцртавања. Flutter позива shouldRepaint при сваком ажурирању стабла виџета које утиче на CustomPainter. Ако метода врати false, већ нацртана слика се сматра важећом и paint се не позива. Типична имплементација: return oldDelegate.someField != someField — поређење поља старог и новог painter-а за одређивање потребе за поновним исцртавањем.
За анимиране или стално променљиве слике (сатови, индикатори напретка, графикони) shouldRepaint треба да врати true при свакој промени стања. Ако је објекат статичан (лого, икона, позадина), shouldRepaint враћа false након првог исцртавања. Flutter аутоматски кешира резултат paint-а и поново исцртава само при промени величине, провидности или позиву shouldRepaint-а.
Направимо GaugePainter — прилагођени кружни индикатор са анимираним попуњавањем. CustomPainter прима тренутну вредност (progress од 0.0 до 1.0) и исцртава лук са градијентом. У методи shouldRepaint се пореди вредност progress-а ради оптимизације. 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 црта после детета CustomPaint-а, односно изнад њега. Користи се за прекриваче: одсјаје, маске, мерне лењире, анотације.
Разлика између painter-а и foregroundPainter-а је критична за Z-order. Ако желите да нацртате нешто иза садржаја — користите painter. Ако изнад садржаја — foregroundPainter. У неким случајевима можете користити оба painter-а истовремено: позадину кроз painter, ознаку-прекривач кроз foregroundPainter. То омогућава раздвајање логике различитих графичких слојева на две одвојене класе CustomPainter-а.
Параметар size у методи paint је исти за оба painter-а и одређује се величином области одређене за CustomPaint. Величина се може задати експлицитно кроз size-параметар у CustomPaint-у или имплицитно кроз родитељски контејнер. Ако је size једнак Size.zero, paint се не позива — Flutter прескаче исцртавање за нулту област. Ово понашање треба узети у обзир при анимираном појављивању елемента.
Главно правило перформанси CustomPainter-а — не креирати објекте унутар методе paint. Canvas, Paint, Path, Rect, Offset и други објекти цртања треба да се креирају у конструктору CustomPainter-а или да се кеширају. Креирање објеката унутар paint-а доводи до алокација на сваком кадру, што при 60 кадрова у секунди изазива честа сакупљања смећа и трзање анимације (jank).
Кеширање Path-а значајно убрзава исцртавање понављајућих сложених контура. Ако се контура не мења између кадрова (нпр. позадинска мрежа), креирајте Path једном и користите га више пута. RepaintBoundary — виџет-омотач који спречава поновно исцртавање CustomPainter-а при промени надређеног стабла виџета. Ако је CustomPainter статичан, умотајте га у RepaintBoundary ради изолације.
За анимиране CustomPainter-е са честим прецртавањем користите Ticker или AnimationController са shouldRepaint-ом који враћа true при промени анимиране вредности. Оптимално је поредити анимирано поље у shouldRepaint-у, а не прецртавати на сваком кадру без потребе. Rect кеширање за сложене градијенте: ако градијент не мења боје, можете кеширати Shader кроз Paint.shader.
Често постављана питања
CustomPainter — је класа-уговор која одређује шта и како цртати на Canvas-у. Canvas — сам објекат цртања који пружа методе (drawCircle, drawLine итд.). CustomPainter добија Canvas кроз метод paint и управља његовим коришћењем. Један Canvas могу користити различити CustomPainter-и у различито време.
Не, CustomPainter нема смисла без CustomPaint-а. CustomPaint је виџет који позива метод paint CustomPainter-а и приказује резултат на екрану. Без CustomPaint-а Canvas неће бити креиран и paint се неће извршити. Може се, међутим, имплементирати сопствени RenderObject са Canvas-ом, али је то приступ нижег нивоа.
Користите AnimationController са Ticker-ом, прослеђујући анимирану вредност у CustomPainter кроз параметре конструктора. При промени вредности позовите setState() у виџету који садржи CustomPaint, што покреће shouldRepaint. Ако shouldRepaint врати true, систем позива paint за нови кадар анимације.
Разлог је готово увек shouldRepaint који враћа false. Проверите да ли се поља oldDelegate-а исправно пореде са новим вредностима. Ако се користи променљиви објекат, shouldRepaint можда неће приметити промене јер референца на објекат остаје иста. Користите == или непроменљиве објекте за исправно поређење.
Да, CustomPainter се користи за исцртавање прилагођених декорација у сложеним листама. Међутим, имајте у виду да при преуређивању елемената RepaintBoundary унутар ListView-а кешира исцртавање сваког елемента. CustomPainter треба да буде лаган и да не садржи тешке операције у paint-у како не би успоравао анимацију превлачења.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође