Flutter: PopupMenuButton Kullanımı ve Menü Yapıları
Son güncelleme:
4 dk okuma

PopupMenuButton, ekranda yer kaplamadan ikincil eylemleri sunmanın yoludur: kullanıcı üç nokta ikonuna basar, küçük bir menü açılır, bir öğe seçilir ve menü kapanır. AppBar'a sığmayan "Ayarlar, Yardım, Çıkış" gibi eylemlerde ya da bir liste satırının "Düzenle, Paylaş, Sil" seçeneklerinde kullanırız. Her öğenin bir value değeri olur; hangi öğenin seçildiğini bu değer üzerinden onSelected içinde yakalarız.
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
PopupMenuButton<String>(
onSelected: (String value) {
debugPrint('Seçilen: $value');
},
itemBuilder: (BuildContext context) => const [
PopupMenuItem<String>(
value: 'edit',
child: Text('Düzenle'),
),
PopupMenuItem<String>(
value: 'delete',
child: Text('Sil'),
),
],
)icon ya da child vermezsek buton, platforma uygun "daha fazla" ikonuyla çizilir. En sık kullanıldığı yer AppBar'ın actions listesidir.
Enum ile Kullanım
String değerlerde bir harf hatası ('delete' yerine 'delte') derleyiciden kaçar ve menü sessizce çalışmaz. Bu yüzden değer tipi olarak enum kullanmak en sağlıklısı:
enum MenuAction { edit, delete, share }
PopupMenuButton<MenuAction>(
onSelected: (MenuAction action) {
switch (action) {
case MenuAction.edit:
_editItem();
case MenuAction.delete:
_deleteItem();
case MenuAction.share:
_shareItem();
}
},
itemBuilder: (context) => const [
PopupMenuItem(value: MenuAction.edit, child: Text('Düzenle')),
PopupMenuItem(value: MenuAction.delete, child: Text('Sil')),
PopupMenuItem(value: MenuAction.share, child: Text('Paylaş')),
],
)Dart 3'te gövdesi dolu bir case'in sonuna break yazmaya gerek yok. Daha da güzeli, enum'a yeni bir değer eklediğimizde derleyici eksik case'i bize hatırlatır. Enum'ların başka neler yapabildiğine Dart'ta enum yazısında değinmiştim.
İkonlu Menü Öğeleri
PopupMenuItem<String>(
value: 'settings',
child: Row(
children: const [
Icon(Icons.settings),
SizedBox(width: 10),
Text('Ayarlar'),
],
),
)PopupMenuDivider ve CheckedPopupMenuItem
Öğeleri gruplamak için araya ayırıcı koyabiliriz. Tehlikeli eylemi (Sil) diğerlerinden ayırmak iyi bir alışkanlıktır:
itemBuilder: (context) => const [
PopupMenuItem(value: 'edit', child: Text('Düzenle')),
PopupMenuItem(value: 'copy', child: Text('Kopyala')),
PopupMenuDivider(), // Ayırıcı çizgi
PopupMenuItem(value: 'delete', child: Text('Sil')),
]Menü bir eylem değil de bir tercih gösteriyorsa (örneğin sıralama ölçütü), seçili olanı onay işaretiyle gösteren CheckedPopupMenuItem kullanılır:
CheckedPopupMenuItem(
value: SortBy.date,
checked: _sortBy == SortBy.date,
child: const Text('Tarihe göre'),
)Önemli Özellikler
| Özellik | Açıklama |
|---|---|
onSelected |
Öğe seçildiğinde çağrılır |
onCanceled |
Menü seçim yapılmadan kapatıldığında çağrılır |
itemBuilder |
Menü öğelerini oluşturur |
icon |
Menü butonu ikonu |
child |
Özel buton widget'ı |
tooltip |
Uzun basınca ve ekran okuyucuda görünen açıklama |
position |
PopupMenuPosition.over ya da under |
offset |
Menü konumu ofseti |
shape |
Menü şekli |
color |
Menü arka plan rengi |
enabled |
Menü aktif/pasif durumu |
Özel Buton ile Kullanım
PopupMenuButton<String>(
position: PopupMenuPosition.under, // Menü butonun altında açılsın
onSelected: (value) {},
itemBuilder: (context) => const [
PopupMenuItem(value: 'week', child: Text('Bu hafta')),
PopupMenuItem(value: 'month', child: Text('Bu ay')),
],
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text('Menü', style: TextStyle(color: Colors.white)),
Icon(Icons.arrow_drop_down, color: Colors.white),
],
),
),
)Ne Zaman Kullanılır, Ne Zaman Kullanılmaz?
Popup menü, eylemleri gizler. Bu hem gücü hem zayıflığıdır: ekran sadeleşir ama kullanıcı o eylemin var olduğunu bilmeyebilir.
- Kullan: ikincil ve seyrek kullanılan eylemler, liste satırına özel seçenekler, AppBar'da üçüncü ikondan sonrası.
- Kullanma: ekranın ana eylemi. "Yeni not" gibi bir işlem menüye gömülmez; görünür bir buton ya da FAB olmalı. Bir-iki eylem varsa doğrudan
IconButtonkoymak daha iyidir. - Kullanma: formda değer seçtirmek. Seçilen değerin ekranda görünmesi gerekiyorsa doğru bileşen DropdownButton'dır; popup menü seçimi hatırlamaz, yalnızca eylem tetikler.
- Kullanma: çok sayıda ya da açıklama gerektiren seçenek. Mobilde
showModalBottomSheetdaha geniş ve başparmağa daha yakın bir alan sunar (BottomSheet yazısı). Masaüstünde alt menülü yapılar gerekiyorsa Material 3 ile gelenMenuAnchorbunun için tasarlandı.
Sık Yapılan Hatalar
1. Öğeye value vermemek
Belirti: Öğeye tıklanır, menü kapanır ama onSelected hiç çağrılmaz.
Sebep: value verilmeyen öğe null döndürür ve PopupMenuButton bunu "seçim yapılmadı" olarak yorumlar; onSelected yerine onCanceled çalışır.
Çözüm: Her PopupMenuItem'a bir value vermek ve butonun generic tipini (PopupMenuButton<MenuAction>) yazmak.
2. Dialog ya da sayfa geçişini onTap içinde yapmak
Belirti: PopupMenuItem'ın onTap parametresinde showDialog çağrılır; bazı Flutter sürümlerinde dialog bir an görünüp hemen kapanır.
Sebep: onTap, menü rotası kapanırken çalışır. Eski sürümlerde menüyü kapatan pop çağrısı yeni açılan dialog'u da götürüyordu.
Çözüm: onTap'i yalnızca context gerektirmeyen küçük işler için kullanmak; dialog açma ve sayfa geçişi gibi işleri onSelected içinde yapmak. onSelected menü rotası kapatıldıktan sonra çağrılır, bu yüzden sürümden bağımsız olarak güvenlidir.
3. child ve icon'u birlikte vermek
Belirti: "You can only pass [child] or [icon], not both." assertion hatası.
Çözüm: Sadece ikonu değiştirmek istiyorsak icon, butonun tamamını kendimiz çizmek istiyorsak child kullanırız.
Mini Senaryo: Liste Satırında Düzenle, Paylaş, Sil
Bir not listesi düşünelim. Her satırın sonunda üç nokta menüsü var; "Sil" seçilince önce onay soruluyor, sonra kısa bir bildirim gösteriliyor. Önce satır:
enum NoteAction { edit, share, delete }
ListTile(
title: Text(note.title),
trailing: PopupMenuButton<NoteAction>(
tooltip: 'Not seçenekleri',
onSelected: (action) => _onNoteAction(action, note),
itemBuilder: (context) => const [
PopupMenuItem(value: NoteAction.edit, child: Text('Düzenle')),
PopupMenuItem(value: NoteAction.share, child: Text('Paylaş')),
PopupMenuDivider(),
PopupMenuItem(value: NoteAction.delete, child: Text('Sil')),
],
),
)Sonra eylemleri yöneten metot:
Future<void> _onNoteAction(NoteAction action, Note note) async {
switch (action) {
case NoteAction.edit:
await Navigator.push(
context,
MaterialPageRoute(builder: (_) => EditNotePage(note: note)),
);
case NoteAction.share:
// Paylaşım işlemi
break;
case NoteAction.delete:
final ok = await showDialog<bool>(
context: context,
builder: (dialogContext) => AlertDialog(
title: const Text('Not silinsin mi?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Vazgeç'),
),
FilledButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Sil'),
),
],
),
);
if (ok != true || !mounted) return;
setState(() => _notes.remove(note));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Not silindi')),
);
}
}Menü sadece niyeti bildiriyor; işin kendisi tek bir metotta toplanıyor. Böylece aynı eylemleri yarın bir kaydırma hareketine ya da detay sayfasındaki butona bağlamak kolay olur. Silme onayının ayrıntıları için AlertDialog yazısına bakabilirsiniz. await'ten sonra mounted kontrolü yapıldığına da dikkat: kullanıcı dialog açıkken sayfadan çıkmış olabilir.
Sık Sorulan Sorular
PopupMenuButton'da onSelected neden çalışmıyor?
Büyük olasılıkla seçilen PopupMenuItem'ın value değeri yoktur. Değeri olmayan öğe null döndürür ve bu durumda onSelected yerine onCanceled çağrılır.
Menü öğesi nasıl pasif yapılır?
PopupMenuItem'a enabled: false verilir. Öğe soluk görünür ve tıklanamaz. Menünün tamamını kapatmak için PopupMenuButton'ın enabled parametresi kullanılır.
Menü butonun altında nasıl açılır?
Varsayılan davranışta menü butonun üzerini örterek açılır. position: PopupMenuPosition.under verildiğinde butonun hemen altında açılır; ince ayar için offset kullanılabilir.
PopupMenuButton ile DropdownButton arasındaki fark nedir?
PopupMenuButton bir eylem tetikler ve seçimi saklamaz. DropdownButton ise bir değer seçtirir ve seçili değeri buton üzerinde gösterir; formlar için uygun olan odur.
İlgili Yazılar
Flutter: AppBar Widget ve Özelleştirme
Flutter AppBar rehberi: title, leading, actions ve bottom kullanımı, Material 3'te kaydırınca renk değişimi ve sık yapılan hataların çözümü.
Flutter: BottomSheet ve showModalBottomSheet Kullanımı
Flutter'da showModalBottomSheet ile değer döndürmek, isScrollControlled, useSafeArea, showDragHandle, klavye boşluğu, DraggableScrollableSheet ve kalıcı sheet.
Flutter: SnackBar ve SnackBarAction Kullanımı
Flutter SnackBar ve ScaffoldMessenger kullanımı: Geri Al aksiyonu, floating görünüm, kuyruk sorunu ve await sonrası context hataları.