custompaintcanvasgambarMahir5 mnt baca

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

dart
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:

dart
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

  • shouldRepaint yang selalu true berarti paint dipanggil 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 sebagai repaint: di konstruktor CustomPainter supaya repaint hanya terjadi saat animasi berdenyut, bukan saat parent rebuild.
  • Ukuran nol: CustomPaint tanpa size dan tanpa child bisa berukuran 0x0 sehingga tidak terlihat. Selalu beri size eksplisit atau bungkus dengan SizedBox.
  • Teks di canvas butuh TextPainter (dengan layout() sebelum paint()), bukan widget Text biasa.
  • Anti-aliasing: paint.isAntiAlias default-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 Container dengan BoxDecoration.
  • Potongan bentuk statis yang kompleks. ClipPath dengan CustomClipper sering 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 RepaintBoundary supaya repaint-nya tidak ikut me-repaint widget lain di sekitarnya.

Catatan teknis: paint berjalan 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 ke PictureRecorder sekali, lalu me-render ulang hasilnya sebagai bitmap.

Tantangan

Gambar segitiga

Buat CustomPainter yang menggambar segitiga merah (Path: moveTo, 2x lineTo, close) ukuran 200x200.