Topik ini membahas penanganan sentuhan pengguna di Flutter, mulai dari tombol Material 3, InkWell dengan animasi denyutnya, hingga GestureDetector untuk deteksi gestur tanpa tampilan tombol. Setelah topik ini dibaca, mekanisme yang tepat untuk setiap situasi antarmuka dapat dipilih dengan alasan yang jelas.
Tombol Material 3#
Tombol adalah cara paling umum menangani sentuhan. Semua tombol Material menerima callback onPressed yang dipanggil ketika tombol ditekan, dan tombol menjadi nonaktif apabila onPressed dibiarkan bernilai null. Sejak Flutter 3.16, Material 3 sudah menjadi bawaan, sehingga bentuk dan warna tombol mengikuti spesifikasi Material 3 tanpa pengaturan tambahan.
Empat tombol berikut mencakup kebutuhan yang paling sering muncul.
| Widget | Posisi yang cocok |
|---|---|
FilledButton | aksi utama pada layar, misalnya menyimpan formulir |
ElevatedButton | aksi penting yang perlu menonjol dengan bayangan |
TextButton | aksi sekunder pada dialog atau baris teks |
IconButton | aksi berikon pada bilah aplikasi dan baris daftar |
// tidak-dijalankan
FilledButton(
onPressed: _simpanCatatan,
child: const Text('Simpan catatan'),
)
IconButton(
onPressed: _hapusCatatan,
icon: const Icon(Icons.delete),
)FilledButton juga menyediakan varian FilledButton.tonal untuk aksi pendamping yang tetap terlihat tanpa menarik perhatian dari aksi utama. Semua tombol menerima properti style untuk penyesuaian warna dan bentuk, tetapi penyesuaian semacam itu sebaiknya didelegasikan pada tema aplikasi agar seluruh layar konsisten.
InkWell dan efek ripple#
InkWell membungkus widget apa pun agar merespons sentuhan dengan animasi ripple, yakni denyut tinta khas Material yang menyebar dari titik sentuh. Callback-nya mencakup onTap, onDoubleTap, dan onLongPress. Widget ini umum digunakan untuk kartu atau baris daftar yang perlu terasa interaktif tanpa tampak seperti tombol.
Animasi tinta digambar pada widget Material terdekat di atasnya. Scaffold sudah menyediakannya, sehingga InkWell langsung bekerja di dalam Scaffold. Apabila InkWell berada di luar cakupan Material, solusinya adalah membungkus area tersebut dengan widget Material atau memindahkan InkWell itu ke dalam Scaffold.
Penggunaan paling lazim adalah membungkus baris daftar atau kartu sederhana. InkWell diletakkan sebagai induk dari Padding dan Text, sehingga seluruh area anak menjadi peka sentuhan. Denyut tinta mengikuti bentuk InkWell, dan callback onHover tersedia apabila reaksi terhadap kursor juga dibutuhkan.
GestureDetector#
GestureDetector mendeteksi gestur pada widget anak tanpa menambahkan efek visual apa pun. Callback-nya mencakup ketukan tunggal (onTap), ketukan ganda (onDoubleTap), tekan lama (onLongPress), hingga seretan (onPanUpdate). Widget ini dipilih ketika area interaktif tidak boleh tampak seperti tombol, misalnya latar kartu catatan yang seluruh permukaannya peka sentuhan.
// tidak-dijalankan
GestureDetector(
onTap: _tandaiSelesai,
onDoubleTap: _tandaiPenting,
onLongPress: _arsipkan,
child: KartuCatatan(judul: 'Latihan Flutter'),
)Flutter menentukan maksud sebuah sentuhan melalui gesture arena, yaitu arena tempat beberapa pengenal gestur bersaing memperebutkan satu sentuhan. Arena inilah yang menentukan pemenang ketika beberapa pengenal mengklaim sentuhan yang sama, misalnya widget bersarang yang sama-sama memasang onTap. Akibatnya, ketukan tunggal tidak dilaporkan seketika apabila onDoubleTap juga terpasang, karena Flutter menunggu sejenak kemungkinan munculnya ketukan kedua.
Satu properti yang sering diperlukan adalah behavior. Secara bawaan, GestureDetector hanya mendeteksi sentuhan pada area yang benar-benar digambar oleh anaknya, sehingga area kosong seperti padding tidak ikut peka sentuhan. Mengisi properti tersebut dengan HitTestBehavior.opaque membuat seluruh kotak widget peka sentuhan, termasuk bagian yang transparan.
Demo berikut menyimulasikan aturan keputusan yang sama pada satu kartu. Ketuk kartunya sekali untuk menandainya selesai, ketuk dua kali beruntun untuk menandainya penting, dan tahan kira-kira setengah detik untuk mengarsipkannya. Perhatikan bahwa ketukan tunggal dilaporkan hanya setelah jeda menunggu ketukan kedua berlalu.
Kode sumber demo
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Demo gestur</title>
<style>
body { font-family: sans-serif; margin: 0; padding: 16px; background: #f8fafc; color: #0f172a; }
#kartu {
background: #ffffff; border: 1px solid #cbd5e1; border-radius: 8px;
padding: 20px; text-align: center; user-select: none;
touch-action: none; cursor: pointer;
}
#kartu.penting { background: #fef9c3; }
#kartu.selesai { opacity: 0.55; text-decoration: line-through; }
#kartu.arsip { background: #e2e8f0; }
#log { list-style: none; margin: 12px 0 0; padding: 0; font-size: 13px; }
#log li { padding: 4px 8px; border-bottom: 1px solid #e2e8f0; }
small { color: #475569; }
</style>
</head>
<body>
<div id="kartu">Kartu catatan<br><small>Ketuk, ketuk dua kali, atau tahan</small></div>
<ul id="log"></ul>
<script>
var kartu = document.getElementById('kartu');
var log = document.getElementById('log');
var waktuKetukTerakhir = 0;
var pengaturTekanLama = null;
function catat(pesan) {
var baris = document.createElement('li');
baris.textContent = pesan;
log.insertBefore(baris, log.firstChild);
}
kartu.addEventListener('pointerdown', function () {
pengaturTekanLama = setTimeout(function () {
pengaturTekanLama = null;
kartu.classList.toggle('arsip');
catat('onLongPress: kartu diarsipkan');
}, 500);
});
kartu.addEventListener('pointerup', function () {
if (pengaturTekanLama === null) { return; }
clearTimeout(pengaturTekanLama);
pengaturTekanLama = null;
var sekarang = Date.now();
if (sekarang - waktuKetukTerakhir < 300) {
waktuKetukTerakhir = 0;
kartu.classList.toggle('penting');
catat('onDoubleTap: kartu ditandai penting');
} else {
waktuKetukTerakhir = sekarang;
setTimeout(function () {
if (waktuKetukTerakhir !== 0 && Date.now() - waktuKetukTerakhir >= 300) {
waktuKetukTerakhir = 0;
kartu.classList.toggle('selesai');
catat('onTap: kartu dicentang');
}
}, 320);
}
});
kartu.addEventListener('pointercancel', function () {
if (pengaturTekanLama !== null) {
clearTimeout(pengaturTekanLama);
pengaturTekanLama = null;
}
});
</script>
</body>
</html>Memilih antara InkWell dan GestureDetector#
InkWell pada dasarnya adalah GestureDetector yang dilengkapi efek tinta Material dan syarat keberadaan Material di atasnya. GestureDetector bekerja di mana saja dan menawarkan jenis gestur yang lebih lengkap. Perbedaannya bukan soal keunggulan mutlak, melainkan kesesuaian dengan kebutuhan umpan balik visual. Keduanya juga dapat digunakan berdampingan dalam satu aplikasi, sehingga keputusan diambil per kasus. Tabel berikut merangkum pilihan yang tepat.
| Situasi | Pilihan |
|---|---|
| Baris daftar atau kartu yang butuh umpan balik tinta | InkWell |
Area interaktif tanpa latar Material | GestureDetector |
| Gestur seret, geser, atau skala | GestureDetector |
| Ketukan dengan efek visual khas Material | InkWell |
Kesalahan umum#
- Menempatkan
InkWelltanpaMaterialdi atasnya. ErrorNo Material widget foundmuncul saat area disentuh. Bungkus area dengan widgetMaterialatau pindahkan ke dalamScaffold. - Menggabungkan
onTapdanonDoubleTappada aksi yang membutuhkan respons cepat. Ketukan tunggal menunggu kemungkinan ketukan kedua sebelum dilaporkan. Pisahkan kedua aksi atau gunakan tombol Material. - Menjadikan ketukan ganda sebagai satu-satunya aksi penting. Ketukan ganda mudah terlewat karena menuntut ritme ketukan yang konsisten. Sediakan aksi alternatif seperti tekan lama.
Ringkasan#
- Tombol Material 3 menangani ketukan melalui
onPresseddan menjadi nonaktif apabila nilainyanull. InkWellmemberi efek ripple dan membutuhkanMaterialdi atasnya dalam pohon widget.GestureDetectormendeteksi ketukan, ketukan ganda, tekan lama, dan seretan tanpa efek visual.InkWellcocok untuk umpan balik tinta, sedangkanGestureDetectoruntuk area tanpa latarMaterialdan gestur yang lebih beragam.
Referensi#
- GestureDetector — Flutter
— Dokumentasi resmi widget
GestureDetectorbeserta daftar gestur yang dikenalinya. - Handle user input — Flutter — Panduan resmi menangani ketukan dan masukan pengguna lainnya.
- Add interactivity to your app — Flutter
— Panduan resmi membuat widget merespons sentuhan menggunakan
setStatedan gestur.