Episode ini memberi jiwa pada tampilan: perbedaan implicit dan explicit animations, penggunaan AnimationController, Tween, dan AnimatedBuilder, Hero animations dan transitions untuk motion design, serta CustomPaint dan advanced UI effects untuk visual yang istimewa.

Animasi bukan hiasan — motion yang baik menjelaskan hubungan antar elemen dan membuat aplikasi terasa premium. Episode 16 memetakan dua jalur animasi di Flutter: implicit animations untuk perubahan sederhana dan explicit animations untuk kontrol penuh.
Kita membahas perbedaan keduanya, AnimationController dengan Tween dan AnimatedBuilder, Hero animations dan page transitions untuk navigasi yang mulus, serta CustomPaint untuk efek visual yang tidak tersedia di widget bawaan.
Implicit animation seperti AnimatedContainer dan AnimatedOpacity meng-animasikan perubahan property secara otomatis. Kalian hanya menyatakan nilai akhir, framework mengurus frame-nya:
AnimatedContainer(
duration: const Duration(milliseconds: 300),
width: _luas,
height: _luas,
color: _warna,
child: const Center(child: Text('Berubah')),
)Saat _luas atau _warna berubah, AnimatedContainer men-tween dari nilai lama ke nilai baru selama 300ms. Implicit animation cocok untuk 80 persen kebutuhan animasi — cukup dan minim kode.
Pilih explicit animation saat butuh kontrol: animasi berulang, interupsi, pemicu dari gesture, atau urutan multi-langkah. Mulai dari implicit, lalu naik ke explicit hanya jika kebutuhan tidak terpenuhi.
AnimationController adalah mesin animasi — ia menghasilkan nilai 0.0 sampai 1.0 dari waktu ke waktu:
class _FadeState extends State<FadeWidget>
with SingleTickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
@override
void initState() {
super.initState();
_controller.forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FadeTransition(
opacity: _controller,
child: const Text('Muncul'),
);
}
}SingleTickerProviderStateMixin menyediakan vsync untuk sinkronisasi frame. _controller.forward() memutar animasi dari 0 ke 1. Perhatikan bahwa dispose membuang controller — aturan yang sama dari episode 15.
Tween memetakan nilai 0-1 ke rentang lain — misalnya 0.0 sampai 200.0 untuk perpindahan. AnimatedBuilder me-rebuild hanya bagian yang bergantung pada animasi:
AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 200 * _controller.value),
child: child,
);
},
child: const Text('Geser ke bawah'),
)Transform.translate memindahkan widget berdasarkan _controller.value yang di-mapping lewat perkalian. Parameter child pada AnimatedBuilder di-build sekali dan dipakai ulang — optimasi yang mencegah rebuild child pada tiap frame.
Hero menghubungkan widget di dua halaman dengan animasi terbang ketika navigasi:
Hero(
tag: 'gambar-produk',
child: Image.network('https://example.com/produk.png'),
)Pasang Hero(tag: 'gambar-produk') di halaman pertama dan halaman tujuan dengan tag yang identik. Saat Navigator.push berjalan, Flutter meng-animasikan widget dari posisi awal ke posisi tujuan secara mulus — salah satu efek paling berkesan di aplikasi retail.
Navigasi bisa diberi animasi khusus lewat PageRouteBuilder:
PageRouteBuilder(
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation) =>
const DetailScreen(),
transitionsBuilder: (context, animation, secondaryAnimation, child) {
final curve = CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
);
return FadeTransition(opacity: curve, child: child);
},
)transitionsBuilder memberi kontrol penuh atas animasi transisi. Curves.easeInOut menghaluskan gerakan — hindari linear untuk kesan yang lebih natural.
Ketika tidak ada widget bawaan yang cukup, CustomPaint memanggil painter:
class LingkaranPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = Colors.indigo
..style = PaintingStyle.fill;
canvas.drawCircle(
Offset(size.width / 2, size.height / 2),
size.width / 4,
paint,
);
}
@override
bool shouldRepaint(covariant LingkaranPainter oldDelegate) => false;
}
CustomPaint(
size: const Size(100, 100),
painter: LingkaranPainter(),
)CustomPainter.paint menerima Canvas dan menggambar langsung — drawCircle, drawPath, dan drawLine adalah primitif dasarnya. shouldRepaint mengontrol kapan painter dijalankan ulang; kembalikan false jika tidak bergantung pada state.
Kombinasi toolkit animasi dengan CustomPaint membuka banyak kemungkinan: progress ring, grafik, peta, hingga effect parallax. Untuk gerakan rumit, pertimbangkan flutter_animate dan package animasi komunitas yang matang — gunakan library bila tersedia, tulis painter hanya saat dibutuhkan.
Inti yang harus dibawa pulang:
AnimationController menghasilkan nilai 0-1 dengan vsync dan forward.Tween memetakan nilai; AnimatedBuilder me-rebuild bagian yang bergantung.Hero dengan tag sama menciptakan transisi terbang antar halaman.PageRouteBuilder memberi kontrol penuh atas animasi navigasi.CustomPaint dan CustomPainter menggambar efek yang tidak tersedia di widget bawaan.Di episode 17 selanjutnya kita membahas internationalization dan accessibility — i18n dengan flutter_localizations, dukungan RTL dan locale-aware formatting, accessibility best practices dengan semantics dan focus order, serta inclusive UX dan accessibility testing. Aplikasi kalian siap melayani pengguna global dan beragam.