Flutter: GestureDetector Kullanımı ve Dokunma Olayları
Son güncelleme:
5 dk okuma

GestureDetector, kendi başına tıklanabilir olmayan herhangi bir widget'a dokunma olayları eklememizi sağlar: tek ve çift tıklama, uzun basma, sürükleme, iki parmakla ölçekleme. Ekranda hiçbir şey çizmez, yalnızca çocuğunun üzerindeki parmak hareketlerini dinler. Hazır butonların yetmediği yerde, örneğin bir resmi yakınlaştırırken ya da bir kartı sürüklerken, başvurduğumuz araç budur.
Canlı Demo
Aşağıdaki interaktif örnekte bu widget'ı deneyebilirsiniz:
💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.
Temel Kullanım
Tıklanmasını istediğimiz widget'ı child olarak verip ilgili callback'i doldurmak yeterli:
GestureDetector(
onTap: () {
print('Tıklandı!');
},
child: Container(
padding: const EdgeInsets.all(20),
color: Colors.blue,
child: const Text('Tıkla bana'),
),
)Desteklenen Gesture Türleri
Tıklama Olayları
GestureDetector(
onTap: () => print('Tek tıklama'),
onDoubleTap: () => print('Çift tıklama'),
onLongPress: () => print('Uzun basma'),
onTapDown: (details) => print('Parmak değdi: ${details.localPosition}'),
onTapUp: (details) => print('Parmak kalktı'),
onTapCancel: () => print('Tıklama iptal'),
child: MyWidget(),
)onTapDown ve onTapUp, dokunulan noktanın koordinatını da verir; bir çizim alanında ya da özel bir kaydırıcıda "nereye dokunuldu" bilgisini buradan alırız. onTapCancel ise parmak basıldıktan sonra hareket bir sürüklemeye dönüştüğünde tetiklenir.
Sürükleme (Drag) Olayları
Sürükleme için üç aile vardır: yalnızca yatay, yalnızca dikey ve her yöne (pan). İhtiyacımız olan tek yönü dinlemek hem daha doğru sonuç verir hem de kaydırılabilir listelerle çakışmayı azaltır.
// Yatay sürükleme
GestureDetector(
onHorizontalDragStart: (details) => print('Yatay sürükleme başladı'),
onHorizontalDragUpdate: (details) => print('Delta: ${details.delta.dx}'),
onHorizontalDragEnd: (details) => print('Hız: ${details.primaryVelocity}'),
child: MyWidget(),
)
// Dikey sürükleme
GestureDetector(
onVerticalDragUpdate: (details) => print('Delta: ${details.delta.dy}'),
onVerticalDragEnd: (details) => print('Hız: ${details.primaryVelocity}'),
child: MyWidget(),
)
// Her yöne sürükleme
GestureDetector(
onPanStart: (details) => print('Pan başladı'),
onPanUpdate: (details) => print('Pozisyon: ${details.localPosition}'),
onPanEnd: (details) => print('Pan bitti'),
child: MyWidget(),
)onHorizontalDragEnd içindeki primaryVelocity, parmağın bırakıldığı andaki hızıdır. İşareti yönü söyler: pozitifse sağa, negatifse sola doğru bir savurma yapılmıştır.
Ölçekleme (Scale/Pinch) Olayları
GestureDetector(
onScaleStart: (details) => print('Ölçekleme başladı'),
onScaleUpdate: (details) {
print('Ölçek: ${details.scale}');
print('Rotasyon: ${details.rotation}');
},
onScaleEnd: (details) => print('Ölçekleme bitti'),
child: MyWidget(),
)Burada gözden kaçan bir ayrıntı var: details.scale her hareketin başında 1.0'dan başlar, yani o harekete görelidir. Kalıcı bir yakınlaştırma için başlangıç ölçeğini saklayıp çarpmak gerekir; aşağıdaki mini senaryoda bunu göreceğiz.
GestureDetector mı, InkWell mi?
İkisi de tıklamayı yakalar, fark geri bildirimdedir. GestureDetector hiçbir görsel tepki vermez. InkWell ise Material Design'ın dalga (ripple) efektini çizer ve kullanıcıya "dokunuşun algılandı" der:
InkWell(
onTap: () => print('Tıklandı'),
onLongPress: () => print('Uzun basıldı'),
borderRadius: BorderRadius.circular(8),
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('Ripple Efektli Satır'),
),
)| GestureDetector | InkWell | |
|---|---|---|
| Görsel geri bildirim | Yok | Ripple ve vurgu |
| Sürükleme, ölçekleme | Var | Yok |
| Material atası gerekir mi? | Hayır | Evet |
| Tipik kullanım | Resim, çizim alanı, özel etkileşim | Liste satırı, kart, özel buton |
Kural olarak: buton gibi davranan her şey için InkWell (ya da doğrudan hazır bir buton), tıklamadan fazlası gereken yerlerde GestureDetector. InkWell'in en yaygın kullanım yeri kartlardır; ayrıntısını Card yazısında anlattım.
HitTestBehavior
behavior parametresi, dokunmanın hangi alanda algılanacağını belirler. Varsayılan değer (çocuk varken) deferToChild'dır: yalnızca çocuğun gerçekten bir şey çizdiği noktalar dokunmayı yakalar.
GestureDetector(
behavior: HitTestBehavior.opaque, // Boş alanlarda da algıla
onTap: () => print('Tıklandı'),
child: const SizedBox(
width: 200,
height: 200,
child: Center(child: Text('Tıkla')),
),
)deferToChild: Yalnızca çocuğun dolu kısımları dokunmayı alır. Rengi olmayan bir alanın boşluklarına dokunmak işe yaramaz.opaque: Widget'ın tüm dikdörtgen alanı dokunmayı alır ve arkasındaki widget'lara geçirmez.translucent: Tüm alan dokunmayı alır, ama arkadaki widget'lar da aynı dokunuşu görebilir.
Ne Zaman Kullanılır, Ne Zaman Kullanılmaz?
GestureDetector'ı hazır bir widget'ın karşılamadığı etkileşimlerde kullanırız: çift tıklamayla beğenme, bir görselin üzerinde dokunulan noktayı bulma, özel bir sürükleme davranışı, pinch ile ölçekleme.
Şu işler için ise Flutter'da daha uygun hazır çözümler var:
- Buton davranışı:
ElevatedButton,IconButton,InkWell. Bunlar geri bildirim, odak ve erişilebilirlik bilgilerini kendileri sağlar. BirContainer'ı GestureDetector ile butona çevirdiğimizde bunların hepsini kaybederiz. - Kaydırarak silme:
Dismissible. Animasyonu ve eşik hesabını hazır verir. - Yakınlaştırma ve gezdirme:
InteractiveViewer. Harita ya da büyük görsel gibi içerikler için sınır kontrolüyle birlikte gelir. - Sürükle-bırak:
DraggableveDragTarget. - İçerik kaydırma: ListView gibi kaydırılabilir widget'lar. Kaydırılabilir bir listenin içinde aynı yönde sürükleme dinlemek iki tarafı birbiriyle yarıştırır.
Sık Yapılan Hatalar
1. Boş alanın dokunmayı almaması
Belirti: Bir Row ya da rengi olmayan bir Container'ı GestureDetector ile sardık; yazının üstüne basınca çalışıyor, yazının yanındaki boşluğa basınca çalışmıyor. Sebep varsayılan deferToChild davranışı. Çözüm tek satır:
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: _openDetail,
child: Row(
children: const [Text('Başlık'), Spacer(), Icon(Icons.chevron_right)],
),
)2. InkWell'de Material atasının olmaması ya da ripple'ın görünmemesi
İlk belirti kırmızı ekranda "No Material widget found" hatasıdır: InkWell dalgayı en yakın Material widget'ının üzerine çizer, bulamazsa hata verir. Scaffold, Card ve Dialog bu atayı zaten sağlar; bunların dışındaysak InkWell'i Material ile sarmak gerekir.
İkinci belirti daha sinsidir: hata yok ama ripple görünmüyor. Sebep genellikle InkWell'in çocuğunda renkli bir Container olmasıdır; dalga Material'a çizilir, opak renk de onu örter. Rengi çocuğa değil Material'a vermek sorunu çözer:
Material(
color: Colors.orange,
borderRadius: BorderRadius.circular(8),
child: InkWell(
borderRadius: BorderRadius.circular(8),
onTap: () {},
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('Ripple görünür'),
),
),
)3. onPan ve onScale'i birlikte kullanmak
Belirti: Debug modda "Incorrect GestureDetector arguments" hatası; açıklamasında scale'in pan'i zaten kapsadığı yazar. Hem gezdirme hem yakınlaştırma gerekiyorsa yalnızca onScale* callback'leri kullanılır; parmağın ne kadar kaydığı details.focalPointDelta ile okunur. Aynı şekilde yatay, dikey ve pan ailelerinin üçünü birden tanımlamak da aynı hatayı üretir.
4. onDoubleTap eklenince tek tıklamanın gecikmesi
onTap ve onDoubleTap birlikte tanımlandığında Flutter, ilk dokunuştan sonra ikincisinin gelip gelmeyeceğini görmek için kısa bir süre bekler. Bu yüzden tek tıklama hissedilir biçimde geç tepki verir. Çift tıklama gerçekten gerekli değilse eklememek, gerekliyse tek tıklamaya kritik bir işlev yüklememek en temiz çözümdür.
Mini Senaryo: Yakınlaştırılabilir Fotoğraf
Bir fotoğraf kartı düşünelim: çift tıklamayla beğeniliyor, iki parmakla yakınlaştırılıyor, uzun basınca eski boyutuna dönüyor.
class ZoomablePhoto extends StatefulWidget {
const ZoomablePhoto({super.key, required this.image});
final ImageProvider image;
@override
State<ZoomablePhoto> createState() => _ZoomablePhotoState();
}
class _ZoomablePhotoState extends State<ZoomablePhoto> {
double _scale = 1.0;
double _startScale = 1.0;
bool _liked = false;
@override
Widget build(BuildContext context) {
return GestureDetector(
onDoubleTap: () => setState(() => _liked = !_liked),
onLongPress: () => setState(() => _scale = 1.0),
onScaleStart: (_) => _startScale = _scale,
onScaleUpdate: (details) {
setState(() {
// details.scale bu harekete görelidir, o yüzden başlangıçla çarpıyoruz
_scale = (_startScale * details.scale).clamp(1.0, 4.0);
});
},
child: Stack(
alignment: Alignment.topRight,
children: [
ClipRect(
child: Transform.scale(
scale: _scale,
child: Image(image: widget.image, fit: BoxFit.cover),
),
),
Padding(
padding: const EdgeInsets.all(8),
child: Icon(
_liked ? Icons.favorite : Icons.favorite_border,
color: Colors.white,
),
),
],
),
);
}
}onScaleStart içinde o anki ölçeği saklıyoruz; saklamasaydık her yeni pinch hareketinde fotoğraf 1.0'a geri zıplardı. clamp ile ölçeği sınırlıyoruz, ClipRect de büyüyen görselin kartın dışına taşmasını engelliyor. Burada onTap tanımlamadığımız için çift tıklama gecikmesi sorun olmuyor. Yakınlaştırmanın bir anda değil yumuşak bir geçişle sıfırlanmasını isterseniz AnimatedContainer ve implicit animasyonlar yazısındaki yaklaşım buraya doğrudan uygulanabilir. Tam teşekküllü bir görsel gezdirme gerekiyorsa da kendi matematiğimizi yazmak yerine InteractiveViewer'a geçmek doğru karar olur.
Olay Özeti
| Olay | Açıklama |
|---|---|
onTap |
Tek tıklama |
onDoubleTap |
Çift tıklama |
onLongPress |
Uzun basma |
onHorizontalDrag* |
Yatay sürükleme |
onVerticalDrag* |
Dikey sürükleme |
onPan* |
Her yöne sürükleme |
onScale* |
Pinch ile yakınlaştırma ve döndürme |
Sık Sorulan Sorular
GestureDetector ile InkWell arasındaki fark nedir?
GestureDetector yalnızca hareketi algılar, görsel bir tepki vermez ve sürükleme ile ölçekleme gibi olayları da destekler. InkWell tıklama ve uzun basmayı Material ripple efektiyle birlikte sunar, ancak bir Material atasına ihtiyaç duyar.
GestureDetector neden boş alana tıklayınca çalışmıyor?
Varsayılan HitTestBehavior.deferToChild davranışında yalnızca çocuğun bir şey çizdiği noktalar dokunmayı alır. behavior parametresini HitTestBehavior.opaque yapmak widget'ın tüm alanını tıklanabilir hâle getirir.
onPan ve onScale birlikte kullanılabilir mi?
Hayır, Flutter bunu debug modda hata olarak bildirir çünkü scale hareketi pan'i zaten kapsar. Yalnızca onScale callback'lerini kullanıp kaydırma miktarını focalPointDelta üzerinden okumak gerekir.
Pinch ile yakınlaştırma için GestureDetector mı, InteractiveViewer mı?
Yakınlaştırma ve gezdirme standart bir ihtiyaçsa InteractiveViewer daha az kodla, sınır kontrolleriyle birlikte çalışır. Ölçeğe başka bir davranış bağlamak ya da hareketi tamamen özelleştirmek gerekiyorsa GestureDetector tercih edilir.
İlgili Yazılar
Flutter: Card Widget ve Tasarım Kullanımı
Flutter Card rehberi: Material 3 Card.filled ve Card.outlined, resimde clipBehavior, InkWell ile tıklanabilir kart, sık hatalar ve ürün kartı örneği.
Flutter: Theme ve ThemeData ile Açık ve Koyu Tema
Flutter'da ThemeData ile tema kurmak: ColorScheme.fromSeed, açık ve koyu tema, ThemeMode, Theme.of ile okuma, TextTheme, bileşen temaları ve ThemeExtension.
Flutter: TextField ve TextEditingController Kullanımı
Flutter TextField rehberi: TextEditingController ve dispose, InputDecoration, klavye tipleri, inputFormatters, şifre alanı, FocusNode ve onChanged farkı.