CustomPainter — είναι μια αφηρημένη κλάση από το Flutter SDK που παρέχει στους προγραμματιστές πλήρη έλεγχο στην απόδοση προσαρμοσμένων 2D γραφικών στο Canvas. Ο προγραμματιστής δημιουργεί μια υποκλάση του CustomPainter και υλοποιεί δύο υποχρεωτικές μεθόδους: paint(Canvas, Size) και shouldRepaint(covariant CustomPainter oldDelegate). Σύμφωνα με το Flutter API Documentation (2026), το CustomPainter χρησιμοποιείται μαζί με το widget CustomPaint, το οποίο αναθέτει την απόδοση στο αντικείμενο CustomPainter σε κάθε καρέ κινούμενου σχεδίου ή αλλαγής κατάστασης.
Κύρια σημεία
CustomPainter — είναι η βασική κλάση Flutter για την υλοποίηση αυθαίρετων 2D γραφικών. Αντικαθιστά την προσέγγιση με την παράκαμψη του onDraw στο Android, παρέχοντας ένα πιο ευέλικτο και δηλωτικό μοντέλο απόδοσης. Ο προγραμματιστής δημιουργεί μια υποκλάση του CustomPainter, παρακάμπτει τη μέθοδο paint, όπου λαμβάνει Canvas και Size, και εκτελεί όλες τις λειτουργίες σχεδίασης στο Canvas. Το widget CustomPaint διαχειρίζεται την κλήση του paint και ενσωματώνει το αποτέλεσμα στο δέντρο widget.
Η βασική διαφορά μεταξύ CustomPainter και άμεσης σχεδίασης μέσω Canvas — ο διαχωρισμός της λογικής απόδοσης (paint) και του ενεργοποιητή επανασχεδίασης (shouldRepaint). Το σύστημα Flutter καλεί το shouldRepaint κάθε φορά που το γονικό widget αλλάζει. Εάν το shouldRepaint επιστρέψει false, το paint δεν καλείται, εξοικονομώντας πόρους. Αυτός ο μηχανισμός είναι ανάλογος του shouldRebuild στο RenderObject και αποτελεί μέρος της αρχιτεκτονικής Flutter με τα τρία δέντρα της (Widget, Element, RenderObject).
CustomPaint — είναι ένα widget που δέχεται τις παραμέτρους painter (στρώμα φόντου) και foregroundPainter (στρώμα προσκηνίου). Και οι δύο παράμετροι δέχονται αντικείμενα CustomPainter. Ο painter φόντου σχεδιάζει πριν από το παιδί του CustomPaint, ενώ ο foregroundPainter μετά. Αυτό επιτρέπει τη χρήση του CustomPaint ως δοχείου με διακοσμητικό φόντο ή επικάλυψη, χωρίς να χάνεται η δυνατότητα ενσωμάτωσης συνηθισμένων widget στο εσωτερικό.
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 — το μέγεθος της περιοχής που έχει διατεθεί για σχεδίαση (πλάτος και ύψος σε λογικά pixel). Το Canvas είναι προρυθμισμένο έτσι ώστε η αρχή συντεταγμένων (0,0) να αντιστοιχεί στην πάνω αριστερή γωνία της περιοχής σχεδίασης. Όλες οι συντεταγμένες ορίζονται σε σχέση με αυτήν την αρχή.
shouldRepaint(covariant CustomPainter oldDelegate) — η υποχρεωτική μέθοδος που βελτιστοποιεί τη συχνότητα επανασχεδίασης. Το Flutter καλεί το shouldRepaint σε κάθε ενημέρωση του δέντρου widget που επηρεάζει το 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 τοποθετείται στο δέντρο widget με την παράμετρο 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 επιτρέπει την κατασκευή αυθαίρετων περιγραμμάτων από γραμμές, καμπύλες Bezier και τόξα. Μετά την κατασκευή, το περίγραμμα γεμίζεται με χρώμα ή περιγράφεται με γραμμή. Στο παράδειγμα, το γράφημα κατασκευάζεται βάσει μιας συστοιχίας σημείων που μιμούνται ένα κυματοειδές σχήμα.
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 — ένα widget-περιτύλιγμα που αποτρέπει την επανασχεδίαση του CustomPainter όταν αλλάζει το ανώτερο δέντρο widget. Εάν το 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 είναι το widget που καλεί τη μέθοδο paint του CustomPainter και εμφανίζει το αποτέλεσμα στην οθόνη. Χωρίς CustomPaint, το Canvas δεν θα δημιουργηθεί και το paint δεν θα εκτελεστεί. Μπορείτε, ωστόσο, να υλοποιήσετε το δικό σας RenderObject με Canvas, αλλά αυτή είναι μια προσέγγιση χαμηλότερου επιπέδου.
Χρησιμοποιήστε AnimationController με Ticker, μεταβιβάζοντας την κινούμενη τιμή στο CustomPainter μέσω των παραμέτρων του κατασκευαστή. Όταν η τιμή αλλάζει, καλέστε το setState() στο widget που περιέχει το CustomPaint, το οποίο ενεργοποιεί το shouldRepaint. Εάν το shouldRepaint επιστρέψει true, το σύστημα καλεί το paint για το νέο καρέ κινούμενου σχεδίου.
Η αιτία είναι σχεδόν πάντα το shouldRepaint που επιστρέφει false. Ελέγξτε εάν τα πεδία του oldDelegate συγκρίνονται σωστά με τις νέες τιμές. Εάν χρησιμοποιείται μεταβλητό αντικείμενο, το shouldRepaint μπορεί να μην παρατηρήσει τις αλλαγές επειδή η αναφορά στο αντικείμενο παραμένει ίδια. Χρησιμοποιήστε == ή αμετάβλητα αντικείμενα για σωστή σύγκριση.
Ναι, το CustomPainter χρησιμοποιείται για τη σχεδίαση προσαρμοσμένων διακοσμήσεων σε σύνθετες λίστες. Ωστόσο, λάβετε υπόψη ότι κατά την αναδιάταξη στοιχείων, το RepaintBoundary μέσα στο ListView αποθηκεύει προσωρινά την απόδοση κάθε στοιχείου. Το CustomPainter πρέπει να είναι ελαφρύ και να μην περιέχει βαριές λειτουργίες στο paint για να μην επιβραδύνει την κίνηση μεταφοράς.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης