İçeriğe geç / Skip to content / Zum Inhalt

Flutter: Drawer Widget ve Yan Menü Kullanımı

Ahmet Balaman

Son güncelleme:

4 dk okuma

FlutterDrawerNavigationDrawerNavigationWidgetMenu
Flutter: Drawer Widget ve Yan Menü Kullanımı

Drawer, ekranın kenarından kayarak açılan yan menüdür. Alt çubuğa sığmayacak kadar çok bölümü olan ya da ayarlar, yardım, hesap gibi ikincil hedefleri bir arada tutmak isteyen uygulamalarda kullanılır. Scaffold'a drawer verdiğiniz anda AppBar'a menü ikonu eklenir, kenardan kaydırma hareketi çalışır ve geri tuşu menüyü kapatır; size kalan, içeriği doldurmak ve seçimden sonra ne olacağına karar vermektir.

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

Scaffold(
  appBar: AppBar(title: const Text('Drawer Örneği')),
  drawer: Drawer(
    child: ListView(
      padding: EdgeInsets.zero,
      children: [
        const DrawerHeader(
          decoration: BoxDecoration(color: Colors.blue),
          child: Text('Menü Başlığı'),
        ),
        ListTile(
          leading: const Icon(Icons.home),
          title: const Text('Ana Sayfa'),
          onTap: () {
            Navigator.pop(context);
          },
        ),
        ListTile(
          leading: const Icon(Icons.settings),
          title: const Text('Ayarlar'),
          onTap: () {
            Navigator.pop(context);
          },
        ),
      ],
    ),
  ),
  body: const Center(child: Text('İçerik')),
)

İçerik olarak ListView kullanmak alışkanlık olsun: menü uzadığında ya da telefon yatay tutulduğunda taşma hatası almazsınız. Navigator.pop(context) burada sayfayı değil, açık olan drawer'ı kapatır.

DrawerHeader ve UserAccountsDrawerHeader

DrawerHeader menünün üstündeki serbest alandır; içine istediğiniz widget'ı koyarsınız. Kullanıcı adı, e-posta ve avatar gösterecekseniz hazır UserAccountsDrawerHeader işinizi kısaltır:

UserAccountsDrawerHeader(
  accountName: const Text('Kullanıcı Adı'),
  accountEmail: const Text('[email protected]'),
  currentAccountPicture: const CircleAvatar(child: Text('KA')),
  decoration: const BoxDecoration(color: Colors.blue),
)

Programatik Açma ve Kapama

Menüyü kendi butonunuzla açmak için iki yol var. İlki Scaffold'a bir GlobalKey vermek:

final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();

Scaffold(
  key: _scaffoldKey,
  drawer: const Drawer(),
  body: ElevatedButton(
    onPressed: () => _scaffoldKey.currentState?.openDrawer(),
    child: const Text('Drawer Aç'),
  ),
)

İkincisi Scaffold.of(context).openDrawer() çağrısıdır; ancak burada context tuzağı vardır, aşağıda hatalar bölümünde anlatıyorum. Kapatmak için Navigator.pop(context) ya da _scaffoldKey.currentState?.closeDrawer() kullanılır.

EndDrawer (Sağ Taraf)

endDrawer aynı widget'ı sağ kenardan açar. Sol menü navigasyon içinken sağ taraf genellikle filtre paneli gibi o ekrana özel içerik taşır. Açmak için openEndDrawer() çağrılır.

Önemli Özellikler

Özellik Açıklama
drawer Sol taraf drawer
endDrawer Sağ taraf drawer
drawerScrimColor Menü açıkken arka planı karartan renk
drawerEdgeDragWidth Kenardan kaydırmanın algılandığı alanın genişliği
drawerEnableOpenDragGesture Kaydırarak açmayı açar ya da kapatır
Drawer(width: ...) Menünün genişliği

Bunların Drawer'ın değil Scaffold'un parametreleri olduğuna dikkat edin; genişlik hariç.

Material 3: NavigationDrawer

Material 3 ile gelen NavigationDrawer, seçili öğeyi hap biçimli bir vurguyla gösterir ve seçim mantığını selectedIndex / onDestinationSelected ikilisiyle yönetir. ListTile'ları elle boyamak yerine hedefleri listelersiniz:

NavigationDrawer(
  selectedIndex: _index,
  onDestinationSelected: (index) {
    Navigator.pop(context);
    setState(() => _index = index);
  },
  children: const [
    Padding(
      padding: EdgeInsets.fromLTRB(28, 16, 16, 10),
      child: Text('Menü'),
    ),
    NavigationDrawerDestination(
      icon: Icon(Icons.inbox_outlined),
      selectedIcon: Icon(Icons.inbox),
      label: Text('Gelen Kutusu'),
    ),
    NavigationDrawerDestination(
      icon: Icon(Icons.send_outlined),
      selectedIcon: Icon(Icons.send),
      label: Text('Gönderilenler'),
    ),
  ],
)

children içine başlık ya da Divider gibi hedef olmayan widget'lar da koyabilirsiniz; index yalnızca NavigationDrawerDestination'lar üzerinden sayılır. Seçimden sonra menü kendiliğinden kapanmaz, Navigator.pop yine sizin işiniz.

Ne Zaman Kullanılır, Ne Zaman Kullanılmaz?

Drawer, beşten fazla üst düzey bölümü olan ya da sık kullanılmayan hedefleri (ayarlar, hakkında, çıkış) gözden uzak tutmak isteyen uygulamalar için uygundur. Bedeli, menünün gizli olmasıdır: kullanıcı bölümleri görmek için önce ikona dokunmak zorundadır.

  • Üç ila beş eşit önemde bölüm varsa BottomNavigationBar daha iyidir; hedefler her an görünür ve tek dokunuşla ulaşılır.
  • Aynı ekranın kardeş içerikleri (Aktif / Tamamlanan gibi) için drawer değil TabBar kullanın.
  • Tablet ve masaüstü genişliğinde gizli menü yerine NavigationRail ya da kalıcı bir yan panel daha doğru olur.
  • Alt çubuk ile drawer'ı birlikte kullanacaksanız aynı hedefi iki yere koymayın; ana bölümler altta, ikincil olanlar drawer'da dursun.

Sık Yapılan Hatalar

1. Scaffold.of() called with a context that does not contain a Scaffold. Bu hata, Scaffold'u oluşturan build metodunun context'i ile Scaffold.of çağırdığınızda çıkar. O context ağaçta Scaffold'un üstündedir, aşağı doğru arama yapamaz. Araya bir Builder koyarak Scaffold'un altından yeni bir context alın:

Scaffold(
  drawer: const Drawer(),
  body: Builder(
    builder: (innerContext) => ElevatedButton(
      onPressed: () => Scaffold.of(innerContext).openDrawer(),
      child: const Text('Menüyü Aç'),
    ),
  ),
)

Butonu ayrı bir widget sınıfına taşımak da aynı sonucu verir, çünkü o sınıfın build metodu kendi context'ini alır.

2. Menü kapanmadan yeni sayfaya geçmek. Belirti: kullanıcı açılan sayfadan geri döndüğünde drawer hâlâ açık durur. onTap içinde önce menüyü kapatın, sonra yönlendirin:

onTap: () {
  Navigator.pop(context); // drawer kapanır
  Navigator.push(
    context,
    MaterialPageRoute(builder: (_) => const SettingsPage()),
  );
}

3. Başlığın üstünde boş bir şerit kalıyor. ListView varsayılan olarak durum çubuğu kadar üst boşluk ekler; DrawerHeader'ın rengi bu yüzden ekranın en üstüne kadar çıkmaz. padding: EdgeInsets.zero bunu düzeltir.

4. Yatay kaydırılan içerik menüyü açıyor. Sayfada harita, yatay liste ya da kaydırılabilir bir galeri varsa sol kenardaki hareket drawer'ı tetikleyebilir. Scaffold'da drawerEnableOpenDragGesture: false vererek menüyü yalnızca ikonla açılır hale getirin.

Mini Senaryo: Bölüm Seçen ve Ayarlara Giden Menü

Bir öğrenci paneli düşünün: Dersler, Ödevler ve Notlar aynı Scaffold içinde gövdeyi değiştiriyor, Ayarlar ise ayrı bir sayfa olarak açılıyor. İki davranış farklıdır ve menüde ikisini de doğru kurmak gerekir:

class HomeShell extends StatefulWidget {
  const HomeShell({super.key});

  @override
  State<HomeShell> createState() => _HomeShellState();
}

class _HomeShellState extends State<HomeShell> {
  int _index = 0;

  static const _titles = ['Dersler', 'Ödevler', 'Notlar'];
  static const _icons = [Icons.school, Icons.assignment, Icons.grade];

  void _selectSection(int index) {
    Navigator.pop(context); // önce drawer kapanır
    setState(() => _index = index);
  }

  void _openSettings() {
    Navigator.pop(context);
    Navigator.push(
      context,
      MaterialPageRoute(builder: (_) => const SettingsPage()),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(_titles[_index])),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: [
            const DrawerHeader(
              decoration: BoxDecoration(color: Colors.indigo),
              child: Text(
                'Öğrenci Paneli',
                style: TextStyle(color: Colors.white, fontSize: 20),
              ),
            ),
            for (var i = 0; i < _titles.length; i++)
              ListTile(
                leading: Icon(_icons[i]),
                title: Text(_titles[i]),
                selected: i == _index,
                onTap: () => _selectSection(i),
              ),
            const Divider(),
            ListTile(
              leading: const Icon(Icons.settings),
              title: const Text('Ayarlar'),
              onTap: _openSettings,
            ),
          ],
        ),
      ),
      body: Center(child: Text('${_titles[_index]} içeriği')),
    );
  }
}

class SettingsPage extends StatelessWidget {
  const SettingsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Ayarlar')),
      body: const Center(child: Text('Ayarlar')),
    );
  }
}

Bölüm seçiminde yeni sayfa açılmıyor, yalnızca _index değişiyor; bu yüzden geri tuşu uygulamadan çıkar, bölümler arasında gezinmez. selected: i == _index kullanıcıya nerede olduğunu gösterir. Ayarlar ise gerçek bir sayfa geçişi: AppBar'ında geri oku çıkar ve geri dönüldüğünde menü kapalıdır, çünkü önce pop çağrıldı.

Sık Sorulan Sorular

Drawer'ı sağ tarafta nasıl açarım?

Scaffold'un endDrawer parametresini kullanın. actions boşsa AppBar sağ tarafa menü ikonunu kendisi ekler; kendi butonunuzla açmak isterseniz openEndDrawer() çağırın.

Drawer ikonunu nasıl değiştiririm?

AppBar'ın leading alanına kendi IconButton'ınızı koyun ve onPressed içinde Scaffold.of(context).openDrawer() çağırın. context'in Scaffold'un altından gelmesi için butonu Builder ile sarın.

Drawer boş bir paneldir, içeriği ve seçili görünümü siz kurarsınız. NavigationDrawer Material 3 görünümünü ve seçili öğe vurgusunu hazır verir, seçimi index üzerinden yönetir.

Drawer açıkken geri tuşu ne yapar?

Önce menüyü kapatır; sayfa yerinde kalır. Menü kapalıyken basılırsa normal geri davranışı çalışır.

Yorumlar