CustomPaint Dasar
Gambar bebas di canvas: garis, lingkaran, dan path.
Kenapa CustomPaint Ada: Masalah yang Diselesaikan
Widget bawaan Flutter (Container, Card, ClipRRect) hanya bisa membuat bentuk-bentuk standar. Bagaimana kalau desainmu butuh latar gelombang, grafik garis kustom, indikator progres melingkar bergradien, atau elemen game 2D sederhana? Di sinilah CustomPaint masuk: ia memberimu akses langsung ke canvas, kanvas gambar tingkat rendah yang sama dengan yang dipakai framework untuk me-render semuanya.
Dengan CustomPainter, kamu menggambar garis, lingkaran, path, bahkan teks, dengan kontrol penuh atas setiap detail visual.
Contoh Sederhana: Lingkaran
class LingkaranPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.blue
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
50,
paint,
);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
// pemakaian:
CustomPaint(
size: const Size(200, 200),
painter: LingkaranPainter(),
)Metode paint dipanggil setiap kali Flutter perlu menggambar ulang, dengan size sebesar ukuran widget CustomPaint.
Contoh Nyata: Latar Gelombang untuk Header
Kasus yang sangat umum di aplikasi nyata adalah header dengan tepi melengkung seperti gelombang. Ini sulit dibuat rapi dengan widget standar, tapi mudah dengan Path dan kurva Bezier:
class GelombangPainter extends CustomPainter {
final Color warna;
const GelombangPainter(this.warna);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = warna
..style = PaintingStyle.fill;
final path = Path()
..moveTo(0, size.height * 0.7)
..quadraticBezierTo(
size.width * 0.25, size.height,
size.width * 0.5, size.height * 0.7,
)
..quadraticBezierTo(
size.width * 0.75, size.height * 0.4,
size.width, size.height * 0.7,
)
..lineTo(size.width, 0)
..lineTo(0, 0)
..close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant GelombangPainter oldDelegate) {
return oldDelegate.warna != warna;
}
}Perhatikan shouldRepaint di sini membandingkan field warna: repaint hanya terjadi kalau warnanya berubah. Ini pola yang benar untuk painter berparameter.
Edge Case dan Jebakan Performa
shouldRepaintyang selalu true berartipaintdipanggil di setiap frame rebuild, bahkan saat tidak ada yang berubah. Untuk gambar statis, selalu return false atau bandingkan field satu per satu.- Gambar di dalam animasi: kalau painter digerakkan oleh
AnimationController, teruskan animasinya sebagairepaint:di konstruktorCustomPaintersupaya repaint hanya terjadi saat animasi berdenyut, bukan saat parent rebuild. - Ukuran nol:
CustomPainttanpasizedan tanpa child bisa berukuran 0x0 sehingga tidak terlihat. Selalu berisizeeksplisit atau bungkus denganSizedBox. - Teks di canvas butuh
TextPainter(denganlayout()sebelumpaint()), bukan widgetTextbiasa. - Anti-aliasing:
paint.isAntiAliasdefault-nya true untuk tepi yang halus, tapi gambar yang sangat kompleks di setiap frame bisa menurunkan FPS di HP kelas bawah.
Kapan JANGAN Pakai CustomPaint
- Bentuknya bisa dibuat dengan widget standar. Persegi rounded, lingkaran, dan gradien sederhana lebih murah ditulis sebagai
ContainerdenganBoxDecoration. - Potongan bentuk statis yang kompleks.
ClipPathdenganCustomClippersering lebih deklaratif kalau tujuannya hanya memotong widget. - Gambar vektor dari desainer. File SVG (paket
flutter_svg) atau Lottie jauh lebih mudah dikelola daripada path yang ditulis manual. - Setiap painter yang mahal sebaiknya dibungkus
RepaintBoundarysupaya repaint-nya tidak ikut me-repaint widget lain di sekitarnya.
Catatan teknis:
paintberjalan di thread UI, bukan di isolate terpisah. Operasi menggambar yang berat (ribuan path per frame) akan memblokir frame dan menyebabkan jank. Untuk kasus ekstrem, pertimbangkan menggambar kePictureRecordersekali, lalu me-render ulang hasilnya sebagai bitmap.
Tantangan
Gambar segitiga
Buat CustomPainter yang menggambar segitiga merah (Path: moveTo, 2x lineTo, close) ukuran 200x200.