Flutter: Spacer Widget ile Esnek Boşluklar
Son güncelleme:
6 dk okuma

Spacer, Row veya Column içindeki widget'lar arasında esnek, görünmez bir boşluk açar. Sabit bir SizedBox'tan farkı, boşluğun ekran genişledikçe büyümesi ve daraldıkça küçülmesidir. Bu yazı yalnızca Spacer'a odaklanıyor: içinde ne olduğu, flex ile boşlukları nasıl oranladığı, hangi durumda mainAxisAlignment ya da SizedBox'ın daha iyi olduğu ve kaydırılabilir alanlarda neden hata verdiği. Spacer, Expanded ve Flexible arasında hangisini seçeceğinizi merak ediyorsanız Expanded, Flexible ve Spacer karşılaştırmasına, flex hesabının ayrıntısı için Expanded yazısına bakabilirsiniz.
Spacer Nedir?
Spacer'ın kaynak kodu tek satırdır: build metodu Expanded(flex: flex, child: const SizedBox.shrink()) döndürür. Yani Spacer, içi sıfır boyutlu bir kutu olan bir Expanded'dır. Bu tanımdan üç sonuç çıkar:
- Row, Column veya Flex'in doğrudan çocuğu olmak zorundadır, çünkü Expanded öyledir.
- Kendisine ayrılan payın tamamını kaplar; payı, sabit boyutlu kardeşlerden arta kalan alan belirler.
- Görünmezdir ve
childparametresi yoktur; renk, kenarlık ya da dokunma alanı veremezsiniz.
Temel Kullanım
Row içinde Spacer
Row(
children: [
Text('Sol'),
Spacer(),
Text('Sağ'),
],
)"Sol" ve "Sağ" metinleri arasındaki tüm boş alanı Spacer doldurur, böylece metinler iki uca yaslanır.
Column içinde Spacer
Column(
children: [
Text('Üst'),
Spacer(),
Text('Alt'),
],
)"Üst" ve "Alt" arasında kalan tüm dikey alan boşluk olur. Bunun çalışması için Column'un yüksekliğinin sınırlı olması gerekir; Scaffold gövdesi gibi bir yerde sorun yoktur, kaydırılabilir bir alanın içinde ise hata alırsınız (aşağıda).
Tek bir öğeyi sona itmek
Spacer'ın iki widget arasında durması gerekmez. En başa koyduğunuzda arkasındaki öğeyi satırın sonuna iter:
Row(
children: [
Spacer(),
Text('Sağda'),
],
)Bu kod geçerlidir ve mainAxisAlignment: MainAxisAlignment.end ile aynı sonucu verir. Spacer'ın avantajı, ileride sola bir öğe eklediğinizde düzenin kendiliğinden doğru kalmasıdır.
Birden Fazla Spacer
Birden fazla Spacer kullanarak boşlukları eşit şekilde dağıtabilirsiniz:
Row(
children: [
Text('Sol'),
Spacer(),
Text('Orta'),
Spacer(),
Text('Sağ'),
],
)İki Spacer'ın flex değeri aynı (varsayılan 1) olduğu için boş alanı yarı yarıya paylaşırlar. "Sol" ve "Sağ" kenarlara yaslanır, "Orta" iki boşluğun arasında kalır. "Orta" metni tam ortada görünmeyebilir: boşluklar eşittir, ama "Sol" ve "Sağ" metinlerinin genişlikleri farklıysa orta metin biraz kayar.
Flex Parametresi
Spacer'ın flex parametresi ile boşlukların oranını ayarlayabilirsiniz; bu parametre Expanded widget'ının API dokümanındaki flex ile aynı mantıkla çalışır:
Row(
children: [
Text('Sol'),
Spacer(flex: 1), // 1 pay
Text('Orta'),
Spacer(flex: 2), // 2 pay
Text('Sağ'),
],
)İkinci Spacer, birincinin iki katı kadar alan kaplar: "Orta" ile "Sağ" arasındaki boşluk, "Sol" ile "Orta" arasındakinin iki katıdır. flex pozitif bir tam sayı olmalıdır; Spacer(flex: 0) yazmak debug modda assert hatası verir.
Flex Oranları
Column(
children: [
Container(height: 50, color: Colors.red),
Spacer(flex: 1),
Container(height: 50, color: Colors.blue),
Spacer(flex: 3),
Container(height: 50, color: Colors.green),
],
)Önce üç kutunun toplam 150 piksellik yüksekliği düşülür, kalan alan 1+3 = 4 paya bölünür: ilk boşluk kalan alanın %25'ini, ikincisi %75'ini alır. Oranlar toplam yüksekliğe göre değil, her zaman kalan alana göre hesaplanır.
Pratik Kullanım Örnekleri
Örnek 1: App Bar Benzeri Düzen
Gerçek bir üst çubuk için elbette AppBar widget'ı var; burada amaç Spacer'ın ikonları sağa iterken başlığı solda tutmasını görmek:
Row(
children: [
IconButton(icon: Icon(Icons.menu), onPressed: () {}),
Text('Başlık', style: TextStyle(fontSize: 20)),
Spacer(),
IconButton(icon: Icon(Icons.search), onPressed: () {}),
IconButton(icon: Icon(Icons.more_vert), onPressed: () {}),
],
)Örnek 2: Kart Alt Kısmı
Row(
children: [
Text('Fiyat: ₺99'),
Spacer(),
ElevatedButton(
onPressed: () {},
child: Text('Satın Al'),
),
],
)Örnek 3: Butonu Ekranın Altına İtmek
Column(
children: [
Padding(
padding: EdgeInsets.all(16),
child: Text('Üst İçerik'),
),
Spacer(),
Padding(
padding: EdgeInsets.all(16),
child: ElevatedButton(
onPressed: () {},
child: Text('Alt Buton'),
),
),
],
)Bu Column bir Scaffold gövdesindeyse buton ekranın altına yapışır. İçerik ekrana sığmayacak kadar uzayabiliyorsa bu yapı yetmez; kaydırılabilir sürümü aşağıda.
Örnek 4: Üç Bölümlü Düzen
Row(
children: [
Icon(Icons.arrow_back),
Spacer(),
Text('Başlık'),
Spacer(),
Icon(Icons.settings),
],
)İki ikon aynı genişlikte olduğu için başlık tam ortaya gelir. Sağ tarafa ikinci bir ikon eklerseniz başlık sola kayar; kenarlardaki öğeler farklı genişlikteyse gerçek ortalama için Stack ya da AppBar'ın centerTitle özelliği daha güvenilirdir.
Spacer vs SizedBox
SizedBox: Sabit Boşluk
Row(
children: [
Text('Sol'),
SizedBox(width: 20), // Her zaman 20 piksel
Text('Sağ'),
],
)- Sabit boyutta boşluk
- Ekran boyutu değişse bile aynı kalır
- Kaydırılabilir alanlarda da çalışır
- Detaylar için: SizedBox ile boyutlandırma ve boşluk
Spacer: Esnek Boşluk
Row(
children: [
Text('Sol'),
Spacer(), // Kalan tüm boşluğu kaplar
Text('Sağ'),
],
)- Kalan alana göre büyür ya da küçülür
- Yalnızca sınırlı boyutlu bir Row veya Column içinde çalışır
- Responsive düzenlerde kullanışlı
Pratik kural: öğeler arasında tasarımın belirlediği bir mesafe varsa (8, 12, 16 piksel) SizedBox, "geriye ne kalırsa" boşluğu gerekiyorsa Spacer.
Spacer vs Expanded
Spacer, içi boş bir Expanded olduğu için aşağıdaki iki kod aynı sonucu verir:
Spacer Kullanımı
Row(
children: [
Text('Sol'),
Spacer(),
Text('Sağ'),
],
)Expanded ile Eşdeğeri
Row(
children: [
Text('Sol'),
Expanded(child: SizedBox.shrink()),
Text('Sağ'),
],
)Fark: Spacer daha okunabilir ve niyeti açıkça gösterir. Sadece boşluk için Spacer, içerik için Expanded kullanın.
Spacer ile Expanded Birlikte
Row(
children: [
Text('Sabit'),
Spacer(flex: 1),
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
child: Text('Genişleyen'),
),
),
Spacer(flex: 1),
Text('Sabit'),
],
)İki "Sabit" metnin genişliği düşüldükten sonra kalan alan 1+2+1 = 4 paya bölünür: mavi alan yarısını, iki boşluk dörtte birer payını alır. Spacer ve Expanded aynı paylaştırma hesabına katıldığı için boşluklar ile içerik birlikte ölçeklenir.
mainAxisAlignment ile Karşılaştırma
Basit durumlarda Spacer yerine mainAxisAlignment kullanabilirsiniz:
mainAxisAlignment.spaceBetween
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Sol'),
Text('Sağ'),
],
)Spacer Eşdeğeri
Row(
children: [
Text('Sol'),
Spacer(),
Text('Sağ'),
],
)Fark: spaceBetween bütün aralıkları eşitler; Spacer ise boşluğu tam istediğiniz yere koyar ve flex ile oranlamanıza izin verir. İkisini aynı satırda birleştirmeyin: resmi dokümana göre Spacer boş alanın tamamını aldığı için spaceBetween, spaceAround ve spaceEvenly görünür bir etki yapmaz, dağıtılacak alan kalmaz. Row'un kendi genişliğini nasıl seçtiği ve hizalamayla ilişkisi mainAxisSize yazısında anlatılıyor.
Spacer Nerede Hata Verir?
1. ListView'in doğrudan çocuğu olarak
// ❌ YANLIŞ: hata verir
ListView(
children: [
Text('Üst'),
Spacer(),
Text('Alt'),
],
)Belirti: Incorrect use of ParentDataWidget. ile başlayan bir hata. Spacer içinde bir Expanded taşıdığı için yalnızca Row, Column ve Flex'in doğrudan çocuğu olabilir; ListView ise çocuklarını Flex olarak değil, kaydırılabilir bir liste olarak dizer. Liste öğeleri arasında boşluk için SizedBox(height: 16) ya da ListView.separated kullanın.
2. SingleChildScrollView içindeki Column'da
// ❌ YANLIŞ: hata verir
SingleChildScrollView(
child: Column(
children: [
Text('Üst'),
Spacer(),
Text('Alt'),
],
),
)Belirti: RenderFlex children have non-zero flex but incoming height constraints are unbounded. Burada Spacer bir Column'un içinde, yani doğru yerde; sorun, kaydırma alanının Column'a sonsuz yükseklik vermesi. Sonsuzdan "kalan alan" hesaplanamaz. Sabit bir boşluk yeterliyse SizedBox kullanın. Kaydırılabilir yapıların neden sonsuz yükseklik sunduğunu ScrollView ve liste widget'ları yazısında anlattım:
// ✅ DOĞRU
SingleChildScrollView(
child: Column(
children: [
Text('Üst'),
SizedBox(height: 100), // Sabit boşluk
Text('Alt'),
],
),
)3. Sınırlı bir alan içinde sorun yok
// ✅ DOĞRU
SizedBox(
height: 300, // Sınırlı yükseklik
child: Column(
children: [
Text('Üst'),
Spacer(),
Text('Alt'),
],
),
)Column'un yüksekliği belli olduğu anda Spacer yeniden çalışır.
Kaydırılabilir Sayfada Butonu Alta Yapıştırmak
Kayıt ve giriş formlarında sık istenen düzen: içerik kısaysa buton ekranın altında dursun, klavye açılınca ya da içerik uzayınca sayfa kaydırılabilsin. SingleChildScrollView dokümanındaki çözüm, Column'a "en az ekran kadar yüksek ol" kuralını vermektir:
LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight - 32),
child: IntrinsicHeight(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Hoş geldin', style: TextStyle(fontSize: 24)),
const SizedBox(height: 12),
const Text('Devam etmek için aşağıdaki butona dokun.'),
const Spacer(),
FilledButton(onPressed: () {}, child: const Text('Devam et')),
],
),
),
),
);
},
)LayoutBuilder görünür alanın yüksekliğini verir, ConstrainedBox Column'u en az o kadar uzatır (32, üst ve alt padding'in toplamı), IntrinsicHeight ise Column'a kesin bir yükseklik vererek Spacer'ın hesaplayabileceği bir "kalan alan" yaratır. İçerik uzarsa Column da uzar ve kaydırma devam eder. Doküman, IntrinsicHeight'ın içeriği iki kez yerleştirdiği için pahalı olduğunu belirtiyor; bu yüzden bu yapıyı kısa formlarda kullanın, uzun listelerde değil. Sabit yükseklikli öğelerde daha ucuz bir yol da var: IntrinsicHeight ve Spacer'ı kaldırıp Column'a mainAxisAlignment: MainAxisAlignment.spaceBetween vermek.
Ne Zaman Kullanmalı?
Spacer kullanın:
- Öğeler arasında ekranla birlikte büyüyüp küçülen bir boşluk istediğinizde
- Boşlukları farklı oranlarda paylaştırmak istediğinizde (
flex) - Bir grubu satırın ya da sütunun sonuna itmek istediğinizde
- Sınırlı boyutlu bir Row veya Column içinde çalışırken
Spacer kullanmayın:
- ListView veya GridView'in doğrudan çocuğu olarak
SingleChildScrollViewiçindeki bir Column'da (yukarıdaki kalıbı kullanmıyorsanız)mainAxisSize: MainAxisSize.minile içeriğine sarılması gereken bir satırda, çünkü Spacer satırı tam genişliğe çeker- Sabit bir boşluk yeterliyse (
SizedBoxkullanın)
Özet
Spacer:
- İçi boş bir
Expanded'dır:Expanded(flex: flex, child: const SizedBox.shrink()) - Row ve Column içinde, kalan alandan pay alarak esnek boşluk oluşturur
flexparametresi ile boşlukları oranlar- Tek başına bir öğeyi sona itmek için de kullanılabilir
- Sonsuz yükseklik sunan kaydırılabilir alanlarda ve Flex olmayan üst widget'larda hata verir
- Boşluk için tasarlanmıştır; içerik için Expanded kullanın
Sık Sorulan Sorular
Spacer ile Expanded(child: SizedBox()) arasında performans farkı var mı?
Hayır. Spacer zaten içinde tam olarak bu yapıyı barındırır; tek fark okunabilirliktir. Boşluk niyetinizi kodda açıkça göstermek için Spacer'ı tercih edin.
Spacer varken spaceBetween neden etkisiz kalıyor?
Spacer, satırdaki boş alanın tamamını kendi payı olarak alır. mainAxisAlignment ise ancak çocuklar yerleştikten sonra artan alanı dağıtır; Spacer varken artan alan sıfırdır. Hangisini kullanacağınıza karar verin: eşit aralıklar için yalnızca spaceBetween, belirli bir yerde ya da oranlı boşluk için yalnızca Spacer.
Spacer'a renk ya da padding verebilir miyim?
Hayır, Spacer'ın child parametresi yoktur ve görünmezdir. Renkli ya da dekorlu esnek bir alan istiyorsanız Expanded(child: Container(color: ...)) kullanın.
İlgili Yazılar
Flutter: Expanded Widget ile Alan Genişletme
Expanded'ın derinlemesine incelemesi: flex oranı nasıl hesaplanır, iç içe kullanım, Row ve Column dışında neden çalışmaz ve en sık görülen üç hata.
Flutter: mainAxisSize ile Alan ve Hizalama Kontrolü
mainAxisSize'ın max ve min farkı, kısıtlarla ilişkisi, mainAxisAlignment'ın neden etkisiz kaldığı ve dialog, bottom sheet, buton grubu tarifleri.
Flutter: Expanded, Flexible ve Spacer Farkı ve Tarifler
Expanded, Flexible ve Spacer arasındaki fark, FlexFit.loose ile tight ayrımı ve üçünü birlikte kullanan dört tarif: araç çubuğu, kart, form, bölünmüş ekran.