Topik ini membahas dua cara menghidupkan tampilan pada Flutter, yaitu animasi implisit dengan widget berawalan Animated dan transisi elemen bersama dengan Hero. Pembahasan juga memetakan perbedaan animasi implisit dengan animasi eksplisit, sehingga pemilihan teknik berdasarkan kebutuhan gerakan, bukan kebiasaan. Satu demo interaktif meniru perilaku AnimatedContainer, sehingga perubahan nilai antarkeadaan dapat diamati langsung.
Animasi implisit dengan AnimatedContainer#
Animasi implisit adalah animasi yang dijalankan framework secara otomatis setiap kali properti widgetnya berubah nilai. Widget berawalan Animated, seperti AnimatedContainer, AnimatedOpacity, dan AnimatedPadding, menyediakan perilaku tersebut: properti yang biasa diisi nilai tetap, kini diisi nilai yang boleh berubah, dan setiap perubahan diinterpolasi secara halus alih-alih terjadi seketika. Satu-satunya tambahan yang wajib adalah properti duration, yaitu lama peralihan dari nilai lama ke nilai baru.
// tidak-dijalankan
class KartuTugas extends StatefulWidget {
const KartuTugas({super.key});
@override
State<KartuTugas> createState() => _KartuTugasState();
}
class _KartuTugasState extends State<KartuTugas> {
bool _menonjol = false;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
width: _menonjol ? 240 : 160,
height: 56,
alignment: Alignment.center,
decoration: BoxDecoration(
color: _menonjol ? Colors.teal.shade700 : Colors.teal.shade100,
borderRadius: BorderRadius.circular(_menonjol ? 28 : 8),
),
child: Text(
_menonjol ? 'Tugas prioritas' : 'Tugas biasa',
style: TextStyle(
color: _menonjol ? Colors.white : Colors.black87,
),
),
),
TextButton(
onPressed: () => setState(() => _menonjol = !_menonjol),
child: const Text('Ganti keadaan kartu'),
),
],
);
}
}Setiap ketukan pada tombol mengubah _menonjol melalui setState, sehingga metode build dijalankan ulang dan AnimatedContainer menerima nilai width, warna, serta radius yang baru. Framework menyimpan nilai sebelumnya, kemudian menghitung nilai antara pada setiap frame sepanjang 300 milidetik. Properti curve berisi Curves.easeInOut yang mengatur laju perhitungan nilai antara itu, misalnya lambat pada awal dan akhir peralihan. Tanpa AnimatedContainer, perubahan properti yang sama terjadi seketika pada satu frame tanpa jeda yang terlihat.
Widget lain pada keluarga ini bekerja dengan cara yang sama, misalnya AnimatedOpacity yang menganimasikan nilai opacity dan AnimatedPadding yang menganimasikan jarak. Pergantian keadaan tetap berupa perubahan data biasa melalui setState, sedangkan pergerakannya ditangani framework. Dengan demikian, jumlah kode animasi yang ditulis hampir nol, dan biaya utamanya hanya menentukan lama serta laju peralihan yang pas.
Mengamati interpolasi pada demo#
Demo berikut meniru perilaku AnimatedContainer pada kartu tugas menggunakan properti transition milik CSS, karena aplikasi Dart tidak dapat dijalankan langsung di halaman. Tekan tombol Ganti mode, lalu amati lebar, warna latar, radius sudut, warna teks, dan label kartu berubah secara bertahap, bukan seketika. Kedua keadaan kartu pada demo bersesuaian dengan dua cabang nilai pada kode Dart sebelumnya, sedangkan peran duration dan curve pada kode tersebut diambil alih oleh properti transition pada CSS.
Selama peralihan, nilai lebar dan warna yang tampil adalah hasil interpolasi antara keadaan lama dan keadaan baru, bukan nilai lama ataupun nilai baru. Perilaku inilah yang dikerjakan AnimatedContainer pada kode Dart setiap kali _menonjol berganti nilai. Peralihan yang halus juga membawa makna, yaitu menegaskan bahwa kartu sedang berpindah keadaan, sehingga pengguna tidak kehilangan jejak perubahan yang baru terjadi.
Perhatikan pula bahwa demo ini tidak memuat penghitung waktu maupun kode animasi per frame. Properti transition pada CSS cukup menyatakan lama dan laju peralihan, sedangkan perhitungan nilai antaranya dikerjakan browser. Struktur yang sama berlaku pada AnimatedContainer, yang menerima duration dan curve sebagai deklarasi, tanpa perhitungan per frame yang ditulis manual.
Hero untuk transisi antarlayar#
Hero adalah widget yang menganimasikan satu elemen bersama (shared element) antara dua layar pada perpindahan Navigator. Kedua layar memasang Hero dengan tag yang sama, yaitu string penanda elemen tersebut. Ketika layar baru di-push, framework menyalin tampilan elemen itu dari posisi dan ukuran lama ke posisi serta ukuran barunya, sehingga pengguna melihat elemen yang sama berpindah, bukan dua elemen berbeda yang muncul dan menghilang.
Contoh yang lazim adalah avatar anggota piket, yang kecil pada layar daftar dan besar pada layar detail. Kode berikut memasang Hero dengan tag yang sama pada kedua layar.
// tidak-dijalankan
// Layar daftar
Hero(
tag: 'avatar-dewi',
child: const CircleAvatar(radius: 24, child: Text('D')),
)
// Layar detail
Hero(
tag: 'avatar-dewi',
child: const CircleAvatar(radius: 64, child: Text('D')),
)Selama transisi berjalan, CircleAvatar membesar dari radius 24 menjadi 64 sambil berpindah posisi, lalu kembali mengecil ketika layar di-pop. Transisi berlaku dua arah karena proses push dan pop diperlakukan sama. Tag berfungsi sebagai kunci pencocokan, sehingga setiap tag hanya boleh dimiliki satu Hero pada satu layar, sedangkan layar berbeda boleh menggunakan tag yang sama persis. Isi child tidak dibatasi jenisnya, sehingga elemen bersama dapat berupa gambar, kartu, atau gabungan widget lainnya.
Implisit versus eksplisit#
Animasi eksplisit adalah animasi yang dikendalikan langsung oleh kode melalui AnimationController, yaitu objek yang memutar waktu animasi dan mengeluarkan nilai dari 0.0 hingga 1.0. Nilai controller dihubungkan ke warna atau ukuran melalui Tween, yaitu kelas yang memetakan rentang angka menjadi rentang nilai lain, misalnya rentang warna atau rentang ukuran. Widget yang menggunakan controller umumnya StatefulWidget yang menyertakan TickerProviderStateMixin agar controller dapat berjalan, dan controller wajib di-dispose saat widget dilepas. Kelas AnimatedContainer sendiri sebenarnya dibangun di atas mekanisme ini, sehingga kedua kategori terhubung erat.
| Aspek | Animasi implisit | Animasi eksplisit |
|---|---|---|
| Widget utama | AnimatedContainer, AnimatedOpacity, dan sejenisnya | AnimationController dengan Tween |
| Pemicu | Perubahan properti pada build | Panggilan forward(), reverse(), atau repeat() |
| Tambahan state | Tidak ada | Controller yang dibuat dan di-dispose |
| Cocok untuk | Peralihan keadaan sederhana | Gerakan berulang, berurutan, atau yang dikendalikan penuh |
Untuk sebagian besar kebutuhan tampilan, animasi implisit sudah memadai dan jumlah kodenya lebih sedikit. Animasi eksplisit baru diperlukan ketika gerakan harus diulang, dirangkai, dibalik arah di tengah peralihan, atau dihitung mengikuti masukan pengguna, misalnya indikator pemuatan yang berputar terus dan urutan kemunculan butir daftar. Kedua kategori menjadi dasar bagi materi animasi tingkat lanjut yang dijelaskan pada dokumentasi resmi.
Kesalahan umum#
Kesalahan pada animasi implisit jarang berupa kerusakan tampilan, tetapi lebih sering berupa animasi yang tidak berjalan sama sekali. Tiga kasus berikut muncul paling sering.
- Lupa mengisi
duration. Properti tersebut wajib diisi, dan kelalaian ini menghentikan build dengan pesan error. Perbaikannya, isi duration sesuai lama peralihan yang diinginkan, misalnya Duration(milliseconds: 300). - Mengubah properti tanpa setState. Nilai berubah di memori tetapi build tidak pernah dipanggil ulang, sehingga tidak ada animasi sama sekali. Perbaikannya, ubah nilai di dalam closure setState pada peristiwa yang relevan.
- Menggunakan tag Hero yang sama dua kali pada satu layar. Framework melempar error ketika transisi berjalan karena lebih dari satu elemen bersama memiliki tag yang sama. Perbaikannya, pastikan setiap tag unik di antara seluruh Hero pada layar yang sama.
Ringkasan#
- Widget berawalan Animated, seperti AnimatedContainer, menganimasikan perubahan properti secara otomatis sepanjang
durationdengan kurva yang dapat diatur melaluicurve. - Hero menyalin satu elemen bersama antarlayar selama push dan pop Navigator, dengan
tagyang sama pada kedua layar dan unik di satu layar. - Animasi implisit cukup untuk peralihan keadaan sederhana, sedangkan animasi eksplisit dengan AnimationController dan Tween diperlukan untuk gerakan yang dikendalikan penuh.
- Pemilihan teknik mengikuti kebutuhan gerakannya, bukan selera.
Referensi#
- Introduction to animations — Flutter — Panduan resmi konsep animasi Flutter, termasuk kategori implisit dan eksplisit.
- AnimatedContainer class — Flutter API — Dokumentasi widget animasi implisit beserta contoh penggunaannya.
- Hero class — Flutter API — Dokumentasi transisi elemen bersama antarlayar beserta aturan tag-nya.
- Curves class — Flutter API
— Daftar kurva interpolasi yang dapat digunakan pada properti
curve.