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
onPressedfonksiyonu ile çalışırStateless 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
Scaffoldiç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:
backgroundColorforegroundColorelevationpaddingshape
🎨 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:
| Durum | Açıklama |
|---|---|
| normal | Aktif |
| disabled | onPressed: null |
| pressed | Basılı |
| hovered | Mouse üzerine gelince |
| focused | Klavye 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:
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.
FilledButton.icon(
onPressed: () => print("Sepete eklendi"),
icon: Icon(Icons.shopping_cart),
label: Text("Sepete Ekle"),
)
İpucu: Eğer
onPressedparametresinenullverirsen, 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:
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.
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:
// 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.
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şim | Kullanıcının nereye dokunduğunu görmesini sağlar. |
| Scale (Ölçekleme) | Fiziksel his | Butonun gerçekten bir "buton" gibi aşağı basıldığı hissini verir. |
| Loading Spinner | Uzun süreli işlemler | Kullanı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:
Her iki sayfada da bir
Herowidget'ı olmalı.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.
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.
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
tagdeğerlerinin sayfada benzersiz olduğundan emin olmalısın. Eğer birListViewiç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.
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.
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?
Süreklilik: Kullanıcı nesnenin kaybolup yeniden oluştuğunu değil, sadece yer değiştirdiğini hisseder.
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.
İnteraktiflik:
InteractiveViewerile 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
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:
Performans:
ListViewveyaGridViewiçinde çok fazla resim varsa,Image.networkyerinecached_network_imagepaketini kullanmalarını öner. Bu, resimlerin her seferinde yeniden yüklenmesini engeller.Hata Yönetimi: Hero animasyonu sırasında "Tag already used" hatası almamak için her
tagisminin sayfada eşsiz (unique) olduğundan emin olunmalıdır.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
Yorum Gönder