Flutter’da Buton Türleri ve Butonların Kişiselleştirilmesi


Flutter’da butonlar, kullanıcı ile uygulama arasındaki en temel etkileşim bileşenleridir.

Modern Flutter (Material 3) ile birlikte buton yapıları daha esnek, daha tutarlı ve tema odaklı hale gelmiştir.


1️⃣ Flutter’da Buton Mantığı (Genel Bakış)

Flutter’da butonlar:

  • Bir Widget’tır

  • onPressed fonksiyonu ile çalışır

  • Stateless veya Stateful widget içinde kullanılabilir

  • Tema (ThemeData) ile global olarak yönetilebilir

📌 onPressed null ise buton pasif (disabled) olur.


2️⃣ Flutter’daki Temel Buton Türleri

🔹 2.1 ElevatedButton (Yükseltilmiş Buton)

En sık kullanılan, gölgeli (elevation) butondur.

ElevatedButton(
  onPressed: () {
    print("Tıklandı");
  },
  child: Text("Giriş Yap"),
)

Özellikleri:

  • Arka plan rengi vardır

  • Gölgeli görünür

  • Ana aksiyonlar için idealdir


🔹 2.2 TextButton (Metin Butonu)

Arka planı olmayan, sade buton.

TextButton(
  onPressed: () {},
  child: Text("Şifremi Unuttum"),
)

Kullanım Alanları:

  • Link benzeri işlemler

  • İkincil aksiyonlar


🔹 2.3 OutlinedButton (Çerçeveli Buton)

Kenarlıklı, içi boş buton.

OutlinedButton(
  onPressed: () {},
  child: Text("Kayıt Ol"),
)

Kullanım Alanları:

  • Alternatif seçenekler

  • İkincil ama dikkat çekici işlemler


🔹 2.4 IconButton (Sadece İkon)

IconButton(
  onPressed: () {},
  icon: Icon(Icons.favorite),
)

📌 Genellikle AppBar, ListTile, Card içinde kullanılır.


🔹 2.5 FloatingActionButton (FAB)

FloatingActionButton(
  onPressed: () {},
  child: Icon(Icons.add),
)

Özellikleri:

  • Dairesel yapı

  • Ana aksiyon için kullanılır

  • Scaffold içinde yer alır


3️⃣ Butonları Kişiselleştirme (Style Kullanımı)

Flutter’da modern butonlar style üzerinden özelleştirilir.


🎨 3.1 ElevatedButton Stil Özelleştirme

ElevatedButton(
  onPressed: () {},
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.green,
    foregroundColor: Colors.white,
    elevation: 6,
    padding: EdgeInsets.symmetric(
      horizontal: 32,
      vertical: 16,
    ),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(20),
    ),
  ),
  child: Text("Devam Et"),
)

Kişiselleştirilebilen Alanlar:

  • backgroundColor

  • foregroundColor

  • elevation

  • padding

  • shape


🎨 3.2 OutlinedButton Kenarlık Özelleştirme

OutlinedButton(
  onPressed: () {},
  style: OutlinedButton.styleFrom(
    side: BorderSide(
      color: Colors.blue,
      width: 2,
    ),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12),
    ),
  ),
  child: Text("Detaylar"),
)

🎨 3.3 TextButton Yazı Stili

TextButton(
  onPressed: () {},
  style: TextButton.styleFrom(
    textStyle: TextStyle(
      fontSize: 18,
      fontWeight: FontWeight.bold,
    ),
    foregroundColor: Colors.red,
  ),
  child: Text("Sil"),
)

4️⃣ Buton Durumları (States)

Flutter butonları farklı durumlara sahiptir:

DurumAçıklama
normalAktif
disabledonPressed: null
pressedBasılı
hoveredMouse üzerine gelince
focusedKlavye odaklı

🎯 MaterialStateProperty Kullanımı

style: ButtonStyle(
  backgroundColor: MaterialStateProperty.resolveWith(
    (states) {
      if (states.contains(MaterialState.pressed)) {
        return Colors.red;
      }
      return Colors.blue;
    },
  ),
),

5️⃣ İkonlu Butonlar

🔹 Icon + Text (ElevatedButton.icon)

ElevatedButton.icon(
  onPressed: () {},
  icon: Icon(Icons.login),
  label: Text("Giriş"),
)

6️⃣ Tema Üzerinden Global Buton Yönetimi

ThemeData(
  elevatedButtonTheme: ElevatedButtonThemeData(
    style: ElevatedButton.styleFrom(
      backgroundColor: Colors.deepPurple,
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(16),
      ),
    ),
  ),
)

📌 Bu sayede tüm ElevatedButton’lar aynı stilde olur.


7️⃣ Gerçek Hayat Örneği: Login Ekranı Butonları

Column(
  children: [
    ElevatedButton(
      onPressed: () {},
      child: Text("Giriş Yap"),
    ),
    OutlinedButton(
      onPressed: () {},
      child: Text("Kayıt Ol"),
    ),
    TextButton(
      onPressed: () {},
      child: Text("Şifremi Unuttum"),
    ),
  ],
)

8️⃣ Yaygın Hatalar ve İpuçları ⚠️

RaisedButton, FlatButton kullanmak (artık deprecated)
styleFrom yerine eski parametreler
✅ Tema kullan
✅ Tutarlı padding ve radius kullan
✅ Buton sayısını abartma


9️⃣ Özet

✔ Flutter’da butonlar Widget temellidir
✔ Modern butonlar style ile özelleştirilir
ThemeData ile global kontrol mümkündür
✔ Doğru buton = doğru kullanıcı deneyimi


Flutter'da butonlar, kullanıcı etkileşiminin merkezinde yer alır. Google'ın Material 3 tasarım diline geçişiyle birlikte buton yapısı daha esnek ve güçlü bir hale geldi.


1. Temel Buton Türleri

Flutter'da en sık kullanılan dört ana buton türü şunlardır:

  • ElevatedButton: Arka plan rengine ve hafif bir gölgeye (elevation) sahip, sayfada öne çıkan butonlardır. Birincil işlemler için kullanılır.

  • FilledButton: Material 3 ile gelen, gölgesiz ancak dolgun bir renk bloğuna sahip butonlardır. Modern arayüzlerde çok popülerdir.

  • OutlinedButton: Sadece sınır çizgileri olan, arka planı şeffaf butonlardır. İkincil işlemler (örneğin "İptal") için idealdir.

  • TextButton: Çerçevesi veya dolgusu olmayan, sadece metinden oluşan butonlardır. Genellikle araç çubuklarında veya diyalog penceresi içindeki seçeneklerde kullanılır.


2. Butonları Özelleştirme (Styling)

Flutter'da butonları özelleştirmenin en modern yolu styleFrom metodunu kullanmaktır. Bu metod, butonun renginden köşelerinin yuvarlaklığına kadar her şeyi kontrol etmeni sağlar.

Renk ve Şekil Değiştirme

Bir butonu uygulamanın renk paletine uydurmak oldukça basittir:

Dart:
ElevatedButton(
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.deepPurple, // Arka plan rengi
    foregroundColor: Colors.white,      // Yazı ve ikon rengi
    shadowColor: Colors.black,          // Gölge rengi
    elevation: 5,                       // Yükseklik
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(12), // Köşe ovalliği
    ),
  ),
  onPressed: () {},
  child: Text('Özel Buton'),
)

Boyutlandırma

Butonun kaplayacağı alanı belirlemek için minimumSize veya fixedSize özelliklerini kullanabilirsin:

  • minimumSize: Butonun en az ne kadar büyük olacağını belirler, içerik artarsa buton büyümeye devam eder.

  • fixedSize: İçerik ne olursa olsun butonu o boyutta sabitler.


3. İkonlu Butonlar ve Durum Yönetimi

Kullanıcı deneyimini artırmak için butonlara ikon eklemek çok yaygındır. Tüm ana buton türlerinin bir .icon yapıcı metodu (constructor) bulunur.

Dart:
FilledButton.icon(
  onPressed: () => print("Sepete eklendi"),
  icon: Icon(Icons.shopping_cart),
  label: Text("Sepete Ekle"),
)

İpucu: Eğer onPressed parametresine null verirsen, buton otomatik olarak "Disabled" (devre dışı) görünümüne geçer. Bu, formların doldurulmasını beklerken harika bir yöntemdir.


4. Gelişmiş Özelleştirme: MaterialStateProperty

Bazen butonun durumuna göre (üzerine gelindiğinde, tıklandığında veya devre dışıyken) farklı renkler almasını istersin. Bunun için ButtonStyle içinde MaterialStateProperty kullanılır.

Örneğin, buton basılıyken renginin değişmesini şu şekilde sağlayabilirsin:

Dart:
style: ButtonStyle(
  backgroundColor: MaterialStateProperty.resolveWith<Color?>(
    (states) {
      if (states.contains(MaterialState.pressed)) return Colors.green;
      return Colors.blue; // Varsayılan renk
    },
  ),
),

Sonuç

Flutter'da butonlar sadece birer tıklama aracı değil, uygulamanın karakterini yansıtan tasarım öğeleridir. styleFrom ile hızlıca, ButtonStyle ile en ince ayrıntısına kadar kontrol sağlayabilirsin.

Flutter'da buton animasyonları, kullanıcıya "Evet, şu an bu işlem gerçekleşiyor" mesajını veren en güçlü geri bildirim mekanizmalarından biridir. Flutter'ın kendi içinde gelen (built-in) efektlerinin yanı sıra, tamamen özel animasyonlar da oluşturabilirsin.


1. Dahili "Ripple" (Dalgalanma) Efekti

Material Design'ın imzası olan bu efekt, Flutter butonlarında varsayılan olarak gelir. Ancak bunu styleFrom içindeki splashFactory ile özelleştirebilirsin.

  • InkRipple: Dalganın merkezden dışarı daha yumuşak yayılmasını sağlar.

  • InkSparkle: (Android 12+ stili) Daha modern ve parıltılı bir efekt sunar.

Dart:
ElevatedButton(
  style: ElevatedButton.styleFrom(
    splashFactory: InkSparkle.constantLightSpread, // Parıltı efekti
  ),
  onPressed: () {},
  child: Text("Parıltılı Tıklama"),
)

2. Durum Değişikliği Animasyonları (Implicit Animations)

Butona tıklandığında boyutunun küçülmesi veya renginin yavaşça değişmesi için AnimatedContainer veya AnimatedDefaultTextStyle gibi widget'lar kullanabiliriz.

Özellikle butona basıldığında oluşan o "içe çökme" (scaling) hissini şu basit mantıkla kurabilirsin:

Dart:
// State içinde bir değişken tanımla
double _scale = 1.0;

GestureDetector(
  onTapDown: (_) => setState(() => _scale = 0.9), // Basıldığında küçül
  onTapUp: (_) => setState(() => _scale = 1.0),   // Bırakıldığında eski haline dön
  child: AnimatedScale(
    scale: _scale,
    duration: Duration(milliseconds: 100),
    child: FilledButton(onPressed: () {}, child: Text("Esnek Buton")),
  ),
)

3. Yükleme (Loading) Animasyonlu Butonlar

Kullanıcı bir butona bastığında bir API isteği gidiyorsa, butonun içindeki metnin yerini bir yükleme ikonuna (Spinner) bırakması profesyonel bir yaklaşımdır.

Dart
bool _isLoading = false;

void _handlePress() async {
  setState(() => _isLoading = true);
  await Future.delayed(Duration(seconds: 2)); // İşlem taklidi
  setState(() => _isLoading = false);
}

ElevatedButton(
  onPressed: _isLoading ? null : _handlePress,
  child: _isLoading 
    ? SizedBox(
        height: 20, 
        width: 20, 
        child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
      )
    : Text("Veriyi Gönder"),
)

Özet: Hangi Animasyon Ne Zaman Kullanılmalı?

Animasyon TürüKullanım AmacıEtki
Splash (Ripple)Standart etkileşimKullanıcının nereye dokunduğunu görmesini sağlar.
Scale (Ölçekleme)Fiziksel hisButonun gerçekten bir "buton" gibi aşağı basıldığı hissini verir.
Loading SpinnerUzun süreli işlemlerKullanıcının "Uygulama dondu mu?" endişesini ortadan kaldırır.

Harika bir seçim! Hero Animasyonları, Flutter'ın sunduğu en "büyüleyici" özelliklerden biridir. Bir widget'ın (bu durumda bir butonun) bir ekrandan diğerine uçarak geçiyormuş ve şekil değiştiriyormuş gibi görünmesini sağlar.

İşte bir butonu, yeni bir sayfanın arka planına veya bir detay kartına dönüştürme rehberi:


1. Hero Animasyonunun Mantığı

Hero animasyonu için iki temel kural vardır:

  1. Her iki sayfada da bir Hero widget'ı olmalı.

  2. Bu iki widget aynı tag (etiket) değerine sahip olmalı.


2. Adım Adım Uygulama

Diyelim ki ana sayfamızda bir "Detay" butonumuz var ve tıklandığında bu buton genişleyerek detay sayfasının başlığına dönüşecek.

A Sayfası: Kaynak Buton

Burada butonu Hero ile sarmalıyoruz ve ona benzersiz bir kimlik veriyoruz.

Dart
Hero(
  tag: 'detay_butonu', // Bu anahtar kelime çok önemli!
  child: ElevatedButton(
    onPressed: () {
      Navigator.push(context, MaterialPageRoute(builder: (_) => DetaySayfasi()));
    },
    child: Text("Detayları Gör"),
  ),
)

B Sayfası: Hedef Görünüm

İkinci sayfada, animasyonun bitmesini istediğimiz yere aynı tag ile başka bir Hero koyuyoruz.

Dart
class DetaySayfasi extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Detay Sayfası")),
      body: Column(
        children: [
          Hero(
            tag: 'detay_butonu', // A sayfasıyla aynı tag
            child: Container(
              width: double.infinity,
              height: 200,
              color: Colors.blue, // Buton buraya "akarak" bu kutuya dönüşecek
              child: Center(child: Text("Hoş Geldiniz", style: TextStyle(color: Colors.white))),
            ),
          ),
        ],
      ),
    );
  }
}

3. İnce Ayarlar: Uçuşu Özelleştirme

Hero animasyonları varsayılan olarak harikadır, ancak bazen "uçuş" (flight) sırasında widget'ın nasıl görüneceğini kontrol etmek isteyebilirsin.

  • flightShuttleBuilder: Widget bir ekrandan diğerine uçarken (havadayken) nasıl görüneceğini belirler. Örneğin, uçarken renginin değişmesini sağlayabilirsin.

  • placeholderBuilder: Widget uçup gittikten sonra, ayrıldığı eski yerinde neyin kalacağını (boşluk mu, gölge mi?) belirler.

Dikkat: Hero animasyonu kullanırken tag değerlerinin sayfada benzersiz olduğundan emin olmalısın. Eğer bir ListView içinde her satırda Hero kullanıyorsan, tag: 'buton_$index' gibi dinamik isimler kullanmak hayat kurtarır.


4. Estetik Bir İpucu: Border Radius Geçişi

Butonun köşeli bir kutuya dönüşmesini istiyorsan, her iki taraftaki widget'ların BorderRadius değerlerinin yumuşak bir şekilde değiştiğini göreceksin. Flutter bunu senin için otomatik olarak hesaplar (interpolate eder), bu da akıcı bir geçiş sağlar.


Harika bir fikir! Hero animasyonu en çok resim galerilerinde kendini gösterir. Küçük bir resmin (thumbnail) üzerine tıkladığınızda, o resmin büyüyerek tam ekran bir görsele dönüşmesi kullanıcıya muazzam bir akışkanlık hissi verir.

Hadi bunu bir galeri senaryosuyla adım adım kodlayalım.


Galeri Senaryosu: Küçük Resimden Tam Ekran Detaya

1. Adım: Galeri Listesi (Küçük Resimler)

Burada resimleri bir GridView içinde listeleyeceğiz. Her resmin kendine has bir tag değeri olması (örneğin resmin URL'i veya ID'si) çok kritiktir.

Dart
GridView.builder(
  gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
  itemCount: 10,
  itemBuilder: (context, index) {
    String imageUrl = 'https://picsum.photos/id/$index/200';
    
    return GestureDetector(
      onTap: () {
        Navigator.push(context, MaterialPageRoute(
          builder: (_) => ResimDetaySayfasi(url: imageUrl, id: index)
        ));
      },
      child: Hero(
        tag: 'resim_$index', // Benzersiz tag: resim_0, resim_1...
        child: Padding(
          padding: const EdgeInsets.all(8.0),
          child: Image.network(imageUrl, fit: BoxFit.cover),
        ),
      ),
    );
  },
)

2. Adım: Detay Sayfası (Tam Ekran Görsel)

Kullanıcı bir resme tıkladığında yeni sayfa açılır. Hero widget'ı, aynı tag'i gördüğü anda resmi eski yerinden alır ve yeni konumuna "uçurarak" büyütür.

Dart
class ResimDetaySayfasi extends StatelessWidget {
  final String url;
  final int id;

  ResimDetaySayfasi({required this.url, required this.id});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black, // Resim öne çıksın
      appBar: AppBar(title: Text("Resim Detayı")),
      body: Center(
        child: Hero(
          tag: 'resim_$id', // Liste sayfasındaki tag ile tam eşleşmeli
          child: InteractiveViewer( // Kullanıcının zoom yapabilmesi için
            child: Image.network(url),
          ),
        ),
      ),
    );
  }
}

Neden Bu Kadar Etkileyici?

  1. Süreklilik: Kullanıcı nesnenin kaybolup yeniden oluştuğunu değil, sadece yer değiştirdiğini hisseder.

  2. Odak Noktası: Göz, hareket eden nesneyi takip eder. Bu da kullanıcının dikkati dağılmadan yeni sayfaya adapte olmasını sağlar.

  3. İnteraktiflik: InteractiveViewer ile birleştirdiğimizde, resim hem büyür hem de kullanıcı tarafından kaydırılıp yakınlaştırılabilir hale gelir.


Küçük Bir İpucu: Hızlı Sayfa Kapatma

Kullanıcı resmi gördükten sonra geri dönmek istediğinde, sayfayı aşağı kaydırarak kapatma (swipe to dismiss) gibi ek animasyonlar eklemek galeriyi daha profesyonel yapar.

Harika! Bu yapıyı PageView ile birleştirmek, galerini gerçek bir mobil uygulama deneyimine dönüştürür. Kullanıcı küçük resme tıklar, resim uçarak tam ekran olur ve sonra sağa sola kaydırarak diğer resimler arasında gezinebilir.

İşte bu gelişmiş yapının kurgusu:


1. Mantık: Dinamik Hero Etiketleri

Buradaki en büyük zorluk, kullanıcı tam ekran modunda resimleri kaydırdığında "Geri" tuşuna basarsa Hero animasyonunun hangi küçük resme geri döneceğidir. Bunu çözmek için tag yapısını çok dikkatli kurmalıyız.


2. PageView ile Tam Ekran Galeri

Kullanıcı küçük resme tıkladığında, ona sadece tıkladığı resmi değil, tüm listeyi ve hangi indekste olduğunu gönderiyoruz.

Detay Sayfası Kodu

Dart
class GaleriSliderSayfasi extends StatelessWidget {
  final List<String> resimListesi;
  final int ilkIndeks;

  GaleriSliderSayfasi({required this.resimListesi, required this.ilkIndeks});

  @override
  Widget build(BuildContext context) {
    // Başlangıç indeksini ayarlamak için controller
    final PageController controller = PageController(initialPage: ilkIndeks);

    return Scaffold(
      backgroundColor: Colors.black,
      body: PageView.builder(
        controller: controller,
        itemCount: resimListesi.length,
        itemBuilder: (context, index) {
          return Center(
            child: Hero(
              // Her resmin tag'i listedekiyle aynı olmalı
              tag: 'resim_$index', 
              child: InteractiveViewer(
                child: Image.network(resimListesi[index]),
              ),
            ),
          );
        },
      ),
    );
  }
}

3. Blog Yazın İçin Teknik Detaylar

Bu konuyu nuritiras.com.tr üzerinde bir eğitim makalesi olarak paylaşırken şu 3 altın kuralı vurgulaman okuyucuların için çok faydalı olacaktır:

  1. Performans: ListView veya GridView içinde çok fazla resim varsa, Image.network yerine cached_network_image paketini kullanmalarını öner. Bu, resimlerin her seferinde yeniden yüklenmesini engeller.

  2. Hata Yönetimi: Hero animasyonu sırasında "Tag already used" hatası almamak için her tag isminin sayfada eşsiz (unique) olduğundan emin olunmalıdır.

  3. Kullanıcı Deneyimi: Tam ekran modunda AppBar'ı şeffaf yaparak veya tamamen gizleyerek resmin tüm ekrana yayılmasını sağlamak görsel derinliği artırır.




Yorumlar

Bu blogdaki popüler yayınlar

Dart Uygulama Sınavı: Pardus ETAP 23 Kurulum Otomasyonu

Dart Programlama Dil Uygulama Sınavı Çalışma Soruları

Pardus Üzerinde Flutter Geliştirme Ortamı Kurulumu