Gestur

Menangani sentuhan pengguna dengan tombol, InkWell, dan GestureDetector.

Daftar isi

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.

WidgetPosisi yang cocok
FilledButtonaksi utama pada layar, misalnya menyimpan formulir
ElevatedButtonaksi penting yang perlu menonjol dengan bayangan
TextButtonaksi sekunder pada dialog atau baris teks
IconButtonaksi berikon pada bilah aplikasi dan baris daftar
dart Aksi utama dan aksi berikon
// 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.

dart Kartu dengan tiga reaksi 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.

SituasiPilihan
Baris daftar atau kartu yang butuh umpan balik tintaInkWell
Area interaktif tanpa latar MaterialGestureDetector
Gestur seret, geser, atau skalaGestureDetector
Ketukan dengan efek visual khas MaterialInkWell

Kesalahan umum#

  • Menempatkan InkWell tanpa Material di atasnya. Error No Material widget found muncul saat area disentuh. Bungkus area dengan widget Material atau pindahkan ke dalam Scaffold.
  • Menggabungkan onTap dan onDoubleTap pada 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 onPressed dan menjadi nonaktif apabila nilainya null.
  • InkWell memberi efek ripple dan membutuhkan Material di atasnya dalam pohon widget.
  • GestureDetector mendeteksi ketukan, ketukan ganda, tekan lama, dan seretan tanpa efek visual.
  • InkWell cocok untuk umpan balik tinta, sedangkan GestureDetector untuk area tanpa latar Material dan gestur yang lebih beragam.

Referensi#