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

Flutter: Spacer Widget ile Esnek Boşluklar

Ahmet Balaman

Son güncelleme:

6 dk okuma

FlutterSpacerLayoutFlexRowColumn
Flutter: Spacer Widget ile Esnek Boşluklar

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 child parametresi 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ğ'),
  ],
)

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
  • SingleChildScrollView içindeki bir Column'da (yukarıdaki kalıbı kullanmıyorsanız)
  • mainAxisSize: MainAxisSize.min ile içeriğine sarılması gereken bir satırda, çünkü Spacer satırı tam genişliğe çeker
  • Sabit bir boşluk yeterliyse (SizedBox kullanı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
  • flex parametresi 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.

Yorumlar