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

Flutter: FloatingActionButton (FAB) Kullanımı ve Özellikleri

Ahmet Balaman

Son güncelleme:

5 dk okuma

FlutterFloatingActionButtonFABheroTagMaterial 3UI
Flutter: FloatingActionButton (FAB) Kullanımı ve Özellikleri

FloatingActionButton (FAB), bir ekranın en önemli tek işlemini temsil eden ve içeriğin üstünde "yüzen" butondur: not uygulamasında "yeni not", e-posta uygulamasında "yaz", harita uygulamasında "konumuma git". Kullanıcı ekrana ilk baktığında gözü oraya gider; bu yüzden FAB'a hangi işi vereceğimiz, nasıl boyayacağımızdan daha önemli bir karardır. Bu yazıda temel kullanımın yanında Material 3 boyutlarını, konumlandırmayı, kaydırırken gizlemeyi ve en sık karşılaşılan heroTag hatasını ele alacağım.

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

FAB, Scaffold'ın floatingActionButton parametresine verilir. Scaffold onu konumlandırır, SnackBar çıktığında yukarı kaydırır ve sayfa geçişlerinde animasyonunu yönetir; bu yüzden FAB'ı Stack içine elle yerleştirmek yerine bu parametreyi kullanmak gerekir.

Scaffold(
  appBar: AppBar(title: const Text('FAB Örneği')),
  body: const Center(child: Text('Merhaba Flutter')),
  floatingActionButton: FloatingActionButton(
    onPressed: () {
      debugPrint('FAB tıklandı!');
    },
    tooltip: 'Ekle', // Uzun basıldığında ve ekran okuyucuda görünür
    child: const Icon(Icons.add),
  ),
)

Material 3'te FAB'ın varsayılan rengi temanın primaryContainer rengidir, ikon rengi de onPrimaryContainer; yani tema doğru kuruluysa hiçbir renk vermeden uyumlu bir buton elde edersiniz.

Boyutlar: small, regular, large ve extended

Material 3 ile FAB'ın üç boyutu ve bir metinli varyantı var. Hepsi ayrı bir constructor olarak sunulur:

FloatingActionButton.small(onPressed: () {}, child: const Icon(Icons.edit)),
FloatingActionButton(onPressed: () {}, child: const Icon(Icons.add)),
FloatingActionButton.large(onPressed: () {}, child: const Icon(Icons.navigation)),
FloatingActionButton.extended(
  onPressed: () {},
  icon: const Icon(Icons.add),
  label: const Text('Yeni Görev'),
),

small ikincil bir işlem için, large ekranın neredeyse tek amacı olan bir işlem için (kamera uygulamasındaki deklanşör gibi), extended ise ikonun tek başına anlaşılmadığı durumlar için düşünülmüştür. Eski mini: true parametresi hâlâ çalışır ama Material 3 projelerinde FloatingActionButton.small tercih edilir.

Özelleştirme

Renk, gölge ve şekil doğrudan widget üzerinden değiştirilebilir:

FloatingActionButton(
  onPressed: () {},
  backgroundColor: Colors.purple, // Buton rengi
  foregroundColor: Colors.white,  // İkon rengi
  elevation: 8.0,                 // Gölge miktarı
  shape: RoundedRectangleBorder(
    borderRadius: BorderRadius.circular(10),
  ),
  child: const Icon(Icons.download),
)

Uygulamadaki bütün FAB'lar aynı görünecekse bu ayarları tek tek yazmak yerine ThemeData içindeki floatingActionButtonTheme alanına bir FloatingActionButtonThemeData vermek daha doğru; hem tekrar azalır hem karanlık tema geçişi tek yerden yönetilir.

Konumlandırma (floatingActionButtonLocation)

FAB'ın yeri Scaffold'ın floatingActionButtonLocation parametresiyle belirlenir. Varsayılan endFloat, yani sağ alt köşe.

Scaffold(
  floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
  floatingActionButton: FloatingActionButton(
    onPressed: () {},
    child: const Icon(Icons.add),
  ),
  bottomNavigationBar: BottomAppBar(
    shape: const CircularNotchedRectangle(), // FAB için oyuk açar
    child: Row(
      children: [
        IconButton(onPressed: () {}, icon: const Icon(Icons.menu)),
        const Spacer(),
        IconButton(onPressed: () {}, icon: const Icon(Icons.search)),
      ],
    ),
  ),
)

Sık kullanılan değerler:

Değer Konum
endFloat Sağ alt (varsayılan)
centerFloat Alt orta
startFloat Sol alt
endDocked, centerDocked BottomAppBar'a gömülü, oyuklu görünüm
endContained BottomAppBar'ın içinde, oyuksuz (Material 3 tarzı)
endTop, miniEndTop AppBar'ın alt kenarına yaslı

Oyuklu (docked) görünüm Material 2'nin imzasıydı; Material 3'te FAB'ın BottomAppBar içinde oyuksuz durduğu endContained konumu daha güncel bir görünüm verir.

Kaydırırken FAB'ı Gizlemek

Uzun listelerde FAB'ın içeriği örtmesini önlemenin yaygın yolu, aşağı kaydırırken gizleyip yukarı kaydırırken geri getirmektir. Listenin kaydırma yönünü NotificationListener<UserScrollNotification> ile dinler, görünürlüğü AnimatedScale ile animasyonlu değiştiririz:

import 'package:flutter/rendering.dart'; // ScrollDirection için

bool _fabVisible = true;

bool _onScroll(UserScrollNotification n) {
  if (n.direction == ScrollDirection.reverse && _fabVisible) {
    setState(() => _fabVisible = false); // Aşağı kaydırılıyor: gizle
  } else if (n.direction == ScrollDirection.forward && !_fabVisible) {
    setState(() => _fabVisible = true);  // Yukarı kaydırılıyor: göster
  }
  return false; // Bildirim yukarı doğru yayılmaya devam etsin
}

// build içinde:
floatingActionButton: AnimatedScale(
  scale: _fabVisible ? 1 : 0,
  duration: const Duration(milliseconds: 200),
  child: FloatingActionButton(onPressed: _add, child: const Icon(Icons.add)),
),

ScrollDirection.reverse kulağa ters gelebilir: içerik yukarı akarken (kullanıcı parmağını yukarı çekerken) kaydırma konumu artar ve Flutter buna "reverse" der.

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

  • Kullan: ekranın tek bir birincil, olumlu işlemi varsa (oluştur, ekle, yaz, gönder). Bir ekranda bir FAB kuralı buradan gelir; iki FAB varsa hangisinin asıl işlem olduğu belirsizleşir.
  • Kullanma: işlem yıkıcıysa (sil, çıkış yap). Bunlar bir menüde ya da liste öğesinin içinde durmalı ve bir AlertDialog ile onaylanmalıdır.
  • Kullanma: birden fazla eşit öncelikli işlem varsa. Doğru yer AppBar'ın actions listesi ya da bir PopupMenuButton'dır.
  • Kullanma: işlem bir formun sonucuysa. Kayıt formunun "Gönder" butonu formun akışında, alanların altında durur; havada süzülen bir buton kullanıcının onu bulmasını zorlaştırır.
  • Kullanma: ekranda zaten bir BottomNavigationBar ya da büyük bir alt sayfa varsa ve FAB onlarla çakışıyorsa. Bu durumda endContained ile BottomAppBar içine almak ya da FAB'dan vazgeçmek gerekir.

Sık Yapılan Hatalar

1. Aynı ekranda iki FAB: heroTag çakışması

Belirti: Sayfa geçişinde uygulama kırmızı hata ekranına düşer: "There are multiple heroes that share the same tag within a subtree."

Sebep: Her FAB kendini otomatik olarak bir Hero ile sarar ve hepsinin varsayılan heroTag'i aynıdır. Aynı ekranda (ya da IndexedStack içindeki farklı Scaffold'larda) iki FAB olduğunda Flutter hangisini uçuracağını bilemez. Bu sistemin kendisi Hero animasyonu hataları yazısında ayrıntılı anlatılıyor.

Çözüm: Her FAB'a farklı bir heroTag vermek ya da animasyon istenmiyorsa null ile kapatmak:

Column(
  mainAxisAlignment: MainAxisAlignment.end,
  children: [
    FloatingActionButton.small(
      heroTag: 'refresh',
      onPressed: () {},
      child: const Icon(Icons.refresh),
    ),
    const SizedBox(height: 12),
    FloatingActionButton(
      heroTag: null, // Hero animasyonunu tamamen kapatır
      onPressed: () {},
      child: const Icon(Icons.add),
    ),
  ],
)

2. FAB listenin son öğesini örtüyor

Belirti: Listenin en altına kaydırıldığında son satırın sağ tarafı FAB'ın altında kalıyor, buton ya da metin tıklanamıyor.

Çözüm: Listeye altta boşluk bırakmak. ListView'in padding parametresine EdgeInsets.only(bottom: 88) gibi bir değer vermek yeterli; ek olarak yukarıdaki gibi kaydırırken gizleme de uygulanabilir.

3. FAB soluk görünüyor ve tepki vermiyor

Belirti: Buton gri, basınca hiçbir şey olmuyor.

Sebep: onPressed parametresine null verilmiş. ElevatedButton'da olduğu gibi FAB'da da null callback butonu devre dışı bırakır. Genellikle onPressed: _isLoading ? null : _save gibi bir koşuldan kaynaklanır ve yükleme bayrağı bir yerde false'a çekilmemiştir.

Çözüm: Bayrağın her yoldan sıfırlandığından emin olmak; try/finally içinde setState(() => _isLoading = false) en güvenli yerdir.

4. Köşesi yuvarlak yerine tam daire bekliyordunuz

Belirti: Material 3'e geçince FAB "kare" oldu.

Sebep: Material 3 tasarım dilinde FAB'ın varsayılan şekli köşeleri yuvarlatılmış bir karedir; bu bir hata değil, yeni varsayılan.

Çözüm: Daireyi geri istiyorsanız shape: const CircleBorder() verin ya da bunu floatingActionButtonTheme üzerinden tüm uygulamaya uygulayın.

Mini Senaryo: Hatırlatıcı Ekleme Ekranı

Bir hatırlatıcı listesi düşünelim. Ekranın tek işlemi yeni hatırlatıcı eklemek; FAB'a basınca tarih seçilir, seçim listeye eklenir ve "Geri Al" seçenekli bir bildirim gösterilir. Liste kaydırılırken FAB gizlenir.

import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

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

  @override
  State<RemindersScreen> createState() => _RemindersScreenState();
}

class _RemindersScreenState extends State<RemindersScreen> {
  final List<DateTime> _reminders = [];
  bool _fabVisible = true;

  bool _onScroll(UserScrollNotification n) {
    if (n.direction == ScrollDirection.reverse && _fabVisible) {
      setState(() => _fabVisible = false);
    } else if (n.direction == ScrollDirection.forward && !_fabVisible) {
      setState(() => _fabVisible = true);
    }
    return false;
  }

  Future<void> _addReminder() async {
    final today = DateTime.now();
    final date = await showDatePicker(
      context: context,
      initialDate: today,
      firstDate: today,
      lastDate: DateTime(today.year + 1),
    );
    if (date == null || !mounted) return;

    setState(() => _reminders.insert(0, date));

    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(
        SnackBar(
          content: const Text('Hatırlatıcı eklendi'),
          action: SnackBarAction(
            label: 'Geri Al',
            onPressed: () => setState(() => _reminders.remove(date)),
          ),
        ),
      );
  }

  @override
  Widget build(BuildContext context) {
    final loc = MaterialLocalizations.of(context);
    return Scaffold(
      appBar: AppBar(title: const Text('Hatırlatıcılar')),
      body: NotificationListener<UserScrollNotification>(
        onNotification: _onScroll,
        child: ListView.builder(
          padding: const EdgeInsets.only(bottom: 88), // Son satır FAB'ın altında kalmasın
          itemCount: _reminders.length,
          itemBuilder: (_, i) => ListTile(
            leading: const Icon(Icons.alarm),
            title: Text(loc.formatFullDate(_reminders[i])),
          ),
        ),
      ),
      floatingActionButton: AnimatedScale(
        scale: _fabVisible ? 1 : 0,
        duration: const Duration(milliseconds: 200),
        child: FloatingActionButton.extended(
          onPressed: _addReminder,
          icon: const Icon(Icons.add_alarm),
          label: const Text('Hatırlatıcı Ekle'),
        ),
      ),
    );
  }
}

Burada üç karar var. Birincisi, buton extended: "+" ikonu tek başına "hatırlatıcı mı, not mu?" sorusunu cevaplamaz, etiket bunu netleştirir. İkincisi, tarih seçiminden sonra mounted kontrolü; showDatePicker bir dialog olduğu için await sırasında sayfa kapanmış olabilir, ayrıntısı DatePicker yazısında. Üçüncüsü, geri bildirim bir dialog değil SnackBar ile veriliyor: ekleme geri alınabilir bir işlemdir, kullanıcının akışını kesmeye gerek yoktur. hideCurrentSnackBar çağrısı da art arda eklemelerde bildirimlerin kuyrukta birikmesini önler.

Sık Sorulan Sorular

"There are multiple heroes that share the same tag" hatası neden çıkar?

Aynı ekranda birden fazla FloatingActionButton olduğunda hepsinin varsayılan heroTag değeri aynıdır ve sayfa geçişinde Hero sistemi hangisini animasyonla taşıyacağını bilemez. Her FAB'a farklı bir heroTag vermek ya da heroTag: null ile animasyonu kapatmak sorunu çözer.

Bir ekranda birden fazla FAB olabilir mi?

Teknik olarak evet, ama tasarım açısından tek bir birincil işlem önerilir. İkinci işlem gerekiyorsa FloatingActionButton.small ile ikincil olduğunu belli etmek ya da işlemi AppBar'a taşımak daha iyi sonuç verir.

FAB kaydırırken nasıl gizlenir?

Listeyi NotificationListener<UserScrollNotification> ile sarıp direction alanına bakarak bir görünürlük bayrağı tutulur; FAB da AnimatedScale ya da AnimatedSlide içine alınarak bu bayrağa göre gösterilir.

FloatingActionButton.extended ne zaman tercih edilmeli?

İkonun tek başına anlamı belirsizse ya da işlem adının görünmesi dönüşümü artırıyorsa. Metin uzunsa ya da ekran dar ise normal FAB ile tooltip kullanmak daha temiz kalır.

Yorumlar