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

Flutter: Align Widget ile İnce Hizalama İşlemleri

Ahmet Balaman

Son güncelleme:

5 dk okuma

FlutterAlignAlignmentStackLayoutHizalama
Flutter: Align Widget ile İnce Hizalama İşlemleri

Align, tek bir çocuğu içinde bulunduğu alanın istediğiniz noktasına yerleştiren widget'tır: sağ üst köşe, alt orta, ya da "yüzde yetmiş sağda, biraz yukarıda" gibi ara bir konum. Center aslında alignment: Alignment.center ile sabitlenmiş bir Align'dır; Align ise dokuz hazır konumun ötesinde -1 ile 1 arasında bir koordinat sistemi sunar. Bu yazıda o koordinat sistemini, FractionalOffset alternatifini, widthFactor/heightFactor'ın Align'ın kendi boyutunu nasıl değiştirdiğini ve Stack içinde Align ile Positioned arasında nasıl seçim yapacağınızı anlatıyorum.

Canlı Demo: Align Widget Örnekleri

Aşağıdaki interaktif örnekte farklı alignment değerlerini deneyebilirsiniz:

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

Temel Kullanım

Container(
  width: 300,
  height: 200,
  color: Colors.grey.shade200,
  child: Align(
    alignment: Alignment.topRight,
    child: Icon(Icons.close, color: Colors.red),
  ),
)

Align iki şey yapar: önce kendi boyutunu belirler (bu örnekte Container'ın verdiği 300x200 alanı kaplar), sonra çocuğunu o alanın içinde alignment değerine göre konumlandırır. Çocuk kendi doğal boyutunda kalır; Align onu büyütmez.

Alignment Koordinat Sistemi

Alignment(x, y) iki sayı alır ve ikisi de -1 ile 1 arasındadır. x için -1 sol kenar, 0 orta, 1 sağ kenar; y için -1 üst, 0 orta, 1 alt. Dokuz hazır sabit bu değerlerin kısaltmasıdır:

Alignment koordinat sistemi: sol üst köşe (-1, -1), merkez (0, 0), sağ alt köşe (1, 1)

Alignment.topLeft      // Alignment(-1, -1)
Alignment.topCenter    // Alignment( 0, -1)
Alignment.topRight     // Alignment( 1, -1)
Alignment.centerLeft   // Alignment(-1,  0)
Alignment.center       // Alignment( 0,  0)
Alignment.centerRight  // Alignment( 1,  0)
Alignment.bottomLeft   // Alignment(-1,  1)
Alignment.bottomCenter // Alignment( 0,  1)
Alignment.bottomRight  // Alignment( 1,  1)

Ara değerler de geçerlidir: Alignment(0.5, 0) çocuğu yatayda merkez ile sağ kenarın tam ortasına koyar, Alignment(0, 0.8) alta yakın ama yapışık olmayan bir konum verir. Değerin ölçüsü, çocuğun boyutu düşüldükten sonra kalan alanın oranıdır; bu yüzden çocuk kaç piksel olursa olsun 1 her zaman "kenara dayalı" demektir.

Aralık dışına çıkmak yasak değildir: Alignment(1.5, 0) çocuğu kısmen sağdan dışarı taşırır. Bir kayan animasyonda işe yarayabilir ama parent kırpmıyorsa (clipBehavior) taşan kısım komşu widget'ların üstüne çizilir.

Sağdan sola dillerde AlignmentDirectional.topStart, centerEnd gibi sabitler Directionality'ye göre kendini çevirir; bir "kapat" butonunu her zaman metnin bittiği tarafa koymak istiyorsanız fiziksel topRight yerine bunları kullanın.

FractionalOffset: 0'dan 1'e

Aynı işi 0 ile 1 arasında düşünmeyi tercih ediyorsanız FractionalOffset(dx, dy) vardır: (0, 0) sol üst, (0.5, 0.5) merkez, (1, 1) sağ alt. Bir tasarım dosyasından "soldan %30" gibi bir değer geliyorsa FractionalOffset(0.3, 0) doğrudan yazılır, Alignment(-0.4, -1) diye dönüştürmek gerekmez. İkisi de AlignmentGeometry türündendir; alignment parametresi her ikisini de kabul eder.

Align'ın Kendi Boyutu: widthFactor ve heightFactor

Align'ın boyutu üç kurala göre belirlenir:

  1. Parent o eksende sınır veriyorsa (bounded) Align mümkün olan en büyük alanı kaplar.
  2. Parent sınır vermiyorsa (Row'un yatay ekseni, Column'un dikey ekseni) Align çocuğu kadar olur.
  3. widthFactor veya heightFactor verilmişse Align o eksende çocuğun boyutunun katı kadar olur ve kural 1'i geçersiz kılar.
Align(
  alignment: Alignment.topLeft,
  widthFactor: 2.0,  // Align genişliği = çocuk genişliği x 2
  heightFactor: 1.5, // Align yüksekliği = çocuk yüksekliği x 1.5
  child: SizedBox(width: 50, height: 50, child: ColoredBox(color: Colors.blue)),
)

Bu Align 100x75 olur; parent ne kadar büyük olursa olsun büyümez. Bir widget'ın etrafında "çocuğuna oranlı" boşluk bırakmak ya da bir kutunun yarısını göstermek gibi işlerde kullanılır. Çoğu zaman bu parametrelere ihtiyacınız olmaz ama kural 1 ve 2 her Align kullanımında devrededir; aşağıdaki hataların çoğu buradan çıkar.

Column İçinde Align

Column, çocuklarına yatayda sınırlı (Column'un genişliği), dikeyde sınırsız alan verir. Bu yüzden Column içindeki bir Align tam genişlikte, çocuğu kadar yükseklikte olur; yani tek bir çocuğu sağa ya da sola itmenin en temiz yoludur:

Column(
  crossAxisAlignment: CrossAxisAlignment.stretch,
  children: [
    const TextField(decoration: InputDecoration(labelText: 'Şifre')),
    Align(
      alignment: Alignment.centerRight,
      child: TextButton(onPressed: () {}, child: const Text('Şifremi unuttum')),
    ),
  ],
)

crossAxisAlignment tüm çocukları etkiler; Align yalnızca sarmaladığı çocuğu. Alan ve stretch etkileşimini mainAxisSize yazısında daha ayrıntılı anlattım.

Stack İçinde Align mı, Positioned mı?

Stack'in konumlandırılmamış çocukları Stack.alignment'a göre yerleşir; Align ile her çocuğa ayrı bir konum verebilirsiniz. Positioned ise top, right gibi piksel değerleriyle çalışır; Stack'in boyutunu nasıl belirlediğini ve Positioned ailesinin tamamını Stack ve Positioned yazısında anlattım. Seçim şöyle yapılır:

  • Konum oransal ise ("sağ üst köşe", "alt orta") Align.
  • Konum piksel cinsinden ise ("üstten 8, sağdan 8") ya da çocuk Stack'in dışına taşacaksa (clipBehavior: Clip.none ile rozet) Positioned.
  • Stack'in boyutu belli değilse (aşağıdaki hata 1) Positioned daha güvenlidir çünkü Stack'in boyutuna katılmaz.

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

Align'a uzanın:

  • Tek bir çocuğu parent'ın bir köşesine veya kenarına yaslamak için.
  • Ara konumlar için (Alignment(0.7, -0.2) gibi); başka hiçbir widget bunu tek satırda vermez.
  • Bir Column içinde tek bir çocuğu sağa/sola itmek için.

Başka araca geçin:

  • Yalnızca ortalama için Center; aynı şey, daha okunur.
  • Piksel ofseti için Positioned (Stack içinde) ya da Transform.translate (herhangi bir yerde).
  • Zaten bir Container varsa Container(alignment: ...); içeride aynı Align'ı kurar.
  • Tüm çocukları aynı anda hizalamak için Row/Column'un mainAxisAlignment ve crossAxisAlignment parametreleri.

Sık Yapılan Hatalar

1. Stack içinde Align tüm Stack'i büyütür

Belirti: İkonun köşesine koymak istediğiniz rozet ekranın en sağına gitti. Stack'in boyutu, konumlandırılmamış en büyük çocuğu kadardır. Align sınırlı bir alanda "mümkün olan en büyük" olmayı seçtiği için Stack de parent'ın verdiği tüm genişliğe yayılır ve rozet o genişliğin köşesine gider.

// Hatalı: Align, Stack'i tüm genişliğe yayar
Stack(
  children: [
    const Icon(Icons.notifications, size: 32),
    Align(alignment: Alignment.topRight, child: _Badge()),
  ],
)

// Doğru: Stack'e somut bir boyut verin...
SizedBox(
  width: 40,
  height: 40,
  child: Stack(
    children: [
      const Align(alignment: Alignment.bottomLeft, child: Icon(Icons.notifications, size: 32)),
      Align(alignment: Alignment.topRight, child: _Badge()),
    ],
  ),
)

// ...ya da Positioned kullanın; Stack'in boyutuna katılmaz
Stack(
  clipBehavior: Clip.none,
  children: [
    const Icon(Icons.notifications, size: 32),
    Positioned(top: -4, right: -4, child: _Badge()),
  ],
)

Material 3'te hazır bir Badge widget'ı da vardır; rozet için önce onu düşünün.

2. Alignment(0.5, 0)'ı "soldan yüzde 50" sanmak

Belirti: Çocuk beklediğinizden daha sağda. Alignment'ta 0 merkezdir; 0.5, merkez ile sağ kenarın ortasıdır, yani soldan yüzde 75'e denk gelir. Yüzde mantığıyla düşünüyorsanız FractionalOffset(0.5, 0) yazın.

3. Row içinde Align hizalamıyor

Belirti: Row'a Align(alignment: Alignment.centerRight, child: Text(...)) koydunuz, metin yerinden kıpırdamadı. Row yatayda sınırsız alan verir; Align çocuğu kadar daralır ve içinde hizalayacak boşluk kalmaz. Çözüm: Align'ı Expanded içine alın ya da Row'un mainAxisAlignment'ını kullanın. Neden Expanded gerektiğini Expanded yazısında bulabilirsiniz.

Mini Senaryo: Görsel Kartı

Bir tarif uygulamasında 200 piksel yüksekliğinde görsel kartları düşünün: sağ üstte favori butonu, sol altta süre etiketi. Stack'in boyutu SizedBox ile belli olduğundan Align burada tam yerine oturur.

class RecipeImageCard extends StatelessWidget {
  const RecipeImageCard({super.key, required this.imageUrl, required this.duration});

  final String imageUrl;
  final String duration;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 200,
      child: Stack(
        fit: StackFit.expand,
        children: [
          Image.network(imageUrl, fit: BoxFit.cover),
          Align(
            alignment: Alignment.topRight,
            child: IconButton.filledTonal(
              onPressed: () {},
              icon: const Icon(Icons.favorite_border),
            ),
          ),
          Align(
            alignment: const Alignment(-0.9, 0.85),
            child: Chip(
              avatar: const Icon(Icons.schedule, size: 16),
              label: Text(duration),
            ),
          ),
        ],
      ),
    );
  }
}

StackFit.expand görselin tüm kartı doldurmasını sağlar; iki Align ise kendi çocuklarını köşelere yerleştirir. Etikette Alignment(-0.9, 0.85) kullandım: tam köşe yerine kenardan biraz içeride durur, ayrıca Padding sarmalamaya gerek kalmaz. Kart genişliği değiştiğinde konumlar oransal olarak korunur; Positioned ile yazsaydım left: 12, bottom: 12 gibi sabitler kullanmam gerekirdi, o da geçerli bir tercih ama tasarım oransal konum istiyorsa Align daha az kod demektir.

Sık Sorulan Sorular

Align ile Center arasındaki fark nedir?

Center, alignment: Alignment.center ile sabitlenmiş bir Align'dır; boyut kuralları ve widthFactor/heightFactor parametreleri aynıdır. Yalnızca ortalama istiyorsanız Center daha okunur, başka bir konum istiyorsanız Align gerekir.

Stack içinde Align mı Positioned mı kullanmalıyım?

Konum oransalsa ve Stack'in boyutu belliyse Align; piksel değeri gerekiyorsa, çocuk Stack'in dışına taşacaksa veya Stack'in boyutu çocuklarından türetiliyorsa Positioned. Positioned, Stack'in boyut hesabına katılmadığı için beklenmedik büyümelere yol açmaz.

Alignment ile FractionalOffset aynı şey mi?

Aynı işi farklı ölçekle yaparlar: Alignment -1 ile 1 arasında ve merkez 0'dır; FractionalOffset 0 ile 1 arasında ve sol üst köşe 0'dır. İkisi de alignment parametresine verilebilir; hangisi tasarım değerlerinizle doğrudan örtüşüyorsa onu seçin.

Align neden çocuğundan büyük?

Parent sınırlı bir alan veriyorsa Align o alanın tamamını kaplar; hizalama ancak böyle bir boşluk varsa anlamlıdır. Align'ı çocuğuna göre boyutlandırmak istiyorsanız widthFactor: 1 ve heightFactor: 1 verin ya da sınırsız bir parent'a koyun.

Yorumlar