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

Flutter: Expanded Widget ile Alan Genişletme

Ahmet Balaman

Son güncelleme:

5 dk okuma

FlutterExpandedFlexLayoutRowColumn
Flutter: Expanded Widget ile Alan Genişletme

Expanded, bir Row veya Column'un içindeki çocuğu, diğer çocuklardan arta kalan alanın tamamını kaplayacak şekilde büyütür. Bir metnin ikonla buton arasında kalan yeri doldurması, bir listenin alt bardan geriye kalan yüksekliği alması, iki panelin ekranı 2:1 paylaşması hep bu widget'ın işidir. Bu yazı Expanded'ın kendisine odaklanıyor: flex hesabı, iç içe kullanım, neden yalnızca Row, Column ve Flex içinde çalıştığı ve konsolda en sık gördüğünüz üç hata. Hangi durumda Expanded yerine Flexible ya da Spacer seçileceğini ve üçünü birlikte kullanan düzen tariflerini Expanded, Flexible ve Spacer karşılaştırmasında ayrıca anlattım; burada yalnızca Expanded'ın kendi davranışı var.

Canlı Demo: Expanded Widget Örnekleri

Expanded ve flex kullanımlarını interaktif olarak deneyin:

💡 Eğer yukarıdaki örnek açılmazsa, DartPad linkine tıklayarak yeni sekmede çalıştırabilirsiniz.

Temel Kullanım

Row içinde Expanded yatay eksende, Column içinde dikey eksende çalışır. Sabit boyutlu çocuklar önce ölçülür, geriye ne kaldıysa Expanded'a verilir:

Row(
  children: [
    Container(width: 50, color: Colors.red),
    Expanded(
      child: Container(color: Colors.blue, child: const Text('Genişledim')),
    ),
    Container(width: 50, color: Colors.green),
  ],
)

Mavi container, kırmızı ve yeşilden sonra kalan tüm genişliği alır. Aynı kodu Column'a taşıyıp width yerine height yazdığınızda mavi alan kalan yüksekliği doldurur. Expanded çapraz eksene karışmaz: Row içindeki Expanded'ın yüksekliği Row'un crossAxisAlignment kuralına göre belirlenir.

Flex Hesabı

Birden fazla Expanded varsa kalan alan flex değerlerine oranla bölünür. Hesap iki adımdır: önce sabit çocuklar düşülür, sonra kalan alan flex toplamına bölünüp her çocuğa payı kadar verilir.

500 piksellik satırda 100 piksel sabit çocuk düşülür, kalan 400 piksel flex 1 için 100, flex 3 için 300 piksel olarak bölünür

Row(
  children: [
    const SizedBox(width: 100, child: Text('Sabit')),
    Expanded(
      flex: 1,
      child: Container(color: Colors.red, child: const Text('1 pay')),
    ),
    Expanded(
      flex: 3,
      child: Container(color: Colors.blue, child: const Text('3 pay')),
    ),
  ],
)

Ekran 500 piksel genişliğindeyse önce 100 piksel sabit çıkar, kalan 400 piksel 1+3 = 4 paya bölünür: kırmızı 100, mavi 300 piksel olur. Sık yapılan bir yanılgı flex: 1 ve flex: 3 değerlerini toplam genişliğin dörtte biri ve dörtte üçü sanmaktır; oran her zaman kalan alan üzerinden hesaplanır. flex yazılmazsa değer 1'dir, yani iki çıplak Expanded alanı yarı yarıya paylaşır.

Flex değerleri birbirine göre anlamlıdır: flex: 2 ve flex: 4 ile flex: 1 ve flex: 2 aynı sonucu verir. Okunabilirlik için en küçük tam sayıları kullanın.

Expanded Aslında Nedir?

Expanded, Flexible(fit: FlexFit.tight) widget'ının kısa yazımıdır. tight demek, çocuğa "tam olarak bu kadar ol" emri vermektir; çocuk daha küçük olmak istese de kalan alanın tamamını kaplar. Flexible'ın varsayılanı olan FlexFit.loose ise "en fazla bu kadar ol" der. Bu ayrım, Expanded'a width: 100 verilmiş bir Container koyduğunuzda genişliğin neden yok sayıldığını da açıklar: sıkı kısıt Container'ın isteğini ezer.

Bir başka sonuç: Expanded, Row'a mainAxisSize: MainAxisSize.min verilmiş olsa bile Row'u tam genişliğe çıkarır, çünkü kalan alanın tamamını istemektedir. Bu etkileşimi mainAxisSize yazısında ayrıca ele aldım.

İç İçe Expanded

Expanded'ın çocuğu bir Row veya Column olabilir ve onun da içinde yeni Expanded'lar bulunabilir. Her Expanded yalnızca doğrudan üstündeki Flex widget'ına bakar:

Row(
  children: [
    Expanded(
      flex: 2,
      child: Column(
        children: [
          Expanded(child: Container(color: Colors.red)),
          Expanded(child: Container(color: Colors.blue)),
        ],
      ),
    ),
    Expanded(
      child: Container(color: Colors.green),
    ),
  ],
)

Dıştaki Row genişliği 2:1 paylaştırır; soldaki Column kendi payının yüksekliğini iki eşit parçaya böler. İçteki Expanded'ların dıştaki Row ile ilgisi yoktur. Bu yapı çalışır çünkü dıştaki Row ekrandan sınırlı bir yükseklik alır ve bu yükseklik Column'a aktarılır. Yükseklik sınırsız olsaydı içteki Expanded'lar hata verirdi; aşağıda bu var.

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

Expanded'ı bir Row veya Column içinde, bir çocuğun "geriye ne kalırsa" kuralıyla boyutlanması gerektiğinde kullanın: uzun metnin ikon ile buton arasını doldurması, içerik alanının alt barın üstündeki tüm yüksekliği alması, sütunların ekranı oranla paylaşması.

Şu durumlarda başka bir araç gerekir:

  • Çocuğun kendi boyutunda kalması ama sığmadığında küçülmesi isteniyorsa Flexible (varsayılan loose). Sohbet balonu gibi içeriğe sarılan düzenlerde Expanded metni gereksiz yere tam genişliğe çeker.
  • Yalnızca boşluk istiyorsanız Spacer. İçi boş bir Expanded(child: SizedBox()) yazmak yerine Spacer(flex: 2) aynı işi okunaklı yapar; ayrıntısı Spacer yazısında.
  • Row veya Column'un dışında tam genişlik istiyorsanız SizedBox(width: double.infinity) ya da Column'a crossAxisAlignment: CrossAxisAlignment.stretch. Expanded, Flex olmayan bir üst widget'ın içinde hata verir.
  • Üst widget'ın belirli bir yüzdesi gerekiyorsa FractionallySizedBox(widthFactor: 0.6). Expanded oranı yalnızca kardeşlerine göre kurar.
  • Kaydırılabilir bir alanda ana eksende sınır yoktur; orada Expanded çalışmaz. Sabit yükseklik verin ya da listeyi doğrudan kaydırılabilir yapın.

Sık Yapılan Hatalar

1. ScrollView içindeki Column'da Expanded

SingleChildScrollView(
  child: Column(
    children: [
      const Text('Başlık'),
      Expanded(child: ListView(children: const [Text('Satır')])), // Hata
    ],
  ),
)

Belirti: RenderFlex children have non-zero flex but incoming height constraints are unbounded. ScrollView, Column'a sonsuz yükseklik sunar ve sonsuzun "kalanı" hesaplanamaz. Üç çıkış yolu var: Expanded'ı kaldırıp içteki listeye SizedBox(height: 300) gibi sabit bir yükseklik vermek; içteki listeye shrinkWrap: true ve physics: NeverScrollableScrollPhysics() verip dıştaki kaydırmayı kullanmak; ya da Column'u ScrollView'dan çıkarıp yalnızca listeyi kaydırılabilir yapmak. Uzun listelerde üçüncüsü en verimlisidir; kaydırma seçenekleri için ScrollView ve liste widget'ları yazısına bakabilirsiniz.

2. Expanded ile Row arasına başka widget koymak

Row(
  children: [
    Padding(
      padding: const EdgeInsets.all(8),
      child: Expanded(child: Text('Metin')), // Hata
    ),
  ],
)

Belirti: Incorrect use of ParentDataWidget. ile başlayan uzun bir hata. Expanded bir "parent data" widget'ıdır; flex bilgisini doğrudan üstündeki Row'a yazar. Arada Padding, Container veya Center varsa bilgi yanlış yere gider. Sırayı ters çevirin: önce Expanded, onun içinde Padding.

3. Expanded içinde sabit genişlik vermek

Belirti: Expanded(child: Container(width: 120, ...)) yazdınız, container yine kalan alanın tamamına yayıldı. tight kısıt Container'ın genişlik isteğini geçersiz kılar. Sabit genişlik istiyorsanız Expanded'ı kaldırın; "en fazla 120 olsun" istiyorsanız Flexible ile ConstrainedBox(constraints: BoxConstraints(maxWidth: 120)) kullanın.

4. Uzun metinde Expanded'ı unutmak

Belirti: Sağ kenarda sarı-siyah şerit ve A RenderFlex overflowed by ... pixels on the right. Row, metnin ne kadar yer isteyeceğini sınırlamaz. Metni Expanded ile sarıp overflow: TextOverflow.ellipsis ekleyin:

Row(
  children: [
    const Icon(Icons.label),
    const SizedBox(width: 8),
    Expanded(
      child: Text(
        'Satıra sığmayacak kadar uzun bir etiket metni',
        overflow: TextOverflow.ellipsis,
      ),
    ),
  ],
)

Mini Senaryo: Ödeme Ekranı

Bir ödeme ekranı düşünün: üstte kaydırılabilir form alanları, altta her zaman görünen "Geri" ve "Ödemeyi tamamla" butonları. Column'daki Expanded formu kalan yüksekliğe yayar; Row'daki iki Expanded butonları 1:2 oranında paylaştırır:

class CheckoutScreen extends StatelessWidget {
  const CheckoutScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Ödeme')),
      body: Column(
        children: [
          Expanded(
            child: ListView(
              padding: const EdgeInsets.all(16),
              children: const [
                TextField(decoration: InputDecoration(labelText: 'Kart numarası')),
                SizedBox(height: 12),
                TextField(decoration: InputDecoration(labelText: 'Son kullanma')),
                SizedBox(height: 12),
                TextField(decoration: InputDecoration(labelText: 'CVC')),
              ],
            ),
          ),
          SafeArea(
            top: false,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                children: [
                  Expanded(
                    child: OutlinedButton(
                      onPressed: () => Navigator.pop(context),
                      child: const Text('Geri'),
                    ),
                  ),
                  const SizedBox(width: 12),
                  Expanded(
                    flex: 2,
                    child: FilledButton(
                      onPressed: () {},
                      child: const Text('Ödemeyi tamamla'),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Bu yapı çalışır çünkü Scaffold gövdeye sınırlı bir yükseklik verir; Column önce alt butonların yüksekliğini ölçer, kalanı Expanded'a bırakır. Klavye açıldığında gövde küçülür, Expanded'ın alanı da otomatik daralır ve liste kaydırılabilir kalır. Aynı listeyi bir SingleChildScrollView içine koysaydınız birinci hatayı alırdınız. Buton satırında SizedBox(width: 12) sabit olduğu için hesaptan önce düşülür; kalan genişlik üçe bölünür ve "Ödemeyi tamamla" iki payı alır.

Sık Sorulan Sorular

Expanded ile Flexible arasındaki fark nedir?

Expanded, Flexible(fit: FlexFit.tight) ile aynıdır: çocuk kalan alanın tamamını kaplamak zorundadır. Flexible'ın varsayılan loose değeri ise çocuğun daha küçük kalmasına izin verir.

Expanded neden Container içinde çalışmıyor?

Expanded yalnızca Row, Column ve Flex'in doğrudan çocuğu olabilir. Başka bir widget'ın içinde Incorrect use of ParentDataWidget hatası alırsınız; tam genişlik için SizedBox(width: double.infinity) kullanın.

"incoming height constraints are unbounded" hatası nasıl çözülür?

Expanded, ana eksende sınırsız kısıt sunan bir üst widget'ın (genellikle SingleChildScrollView veya ListView) içindeki Column'dadır. Expanded'ı kaldırıp sabit yükseklik verin ya da Column'u kaydırma alanından çıkarın.

flex değeri toplam alanın yüzdesi midir?

Hayır. Sabit boyutlu kardeşler önce düşülür, kalan alan flex toplamına bölünür. Row'da 100 piksellik sabit bir çocuk varsa flex: 1 ve flex: 1 toplamın değil, kalanın yarısını alır.

Yorumlar