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

Flutter: MediaQuery ile Ekrana Göre Oranlama

Ahmet Balaman

Son güncelleme:

5 dk okuma

FlutterMediaQueryResponsiveLayoutBuilderSafeAreaLayout
Flutter: MediaQuery ile Ekrana Göre Oranlama

MediaQuery, uygulamanın çalıştığı pencere hakkındaki bilgileri (boyut, güvenli alan, klavye yüksekliği, yön, metin ölçeği) widget ağacına taşıyan widget'tır. "Kart ekranın yüzde 80'i olsun", "geniş ekranda yan menü göster", "klavye açılınca içeriği yukarı kaydır" gibi kararların hepsi buradan beslenir. Bu yazıda önce doğru okuma biçimini (MediaQuery.sizeOf gibi hedefli erişimciler), sonra hangi işin MediaQuery'ye, hangisinin LayoutBuilder veya FractionallySizedBox'a ait olduğunu ve en çok görülen hataları anlatıyorum.

Temel Kullanım

Pencere boyutunu okumanın iki yolu var. Eski alışkanlık MediaQuery.of(context).size yazmaktır; bu, widget'ı MediaQuery'deki her değişikliğe bağlar. Klavye açıldığında viewInsets değişir ve yalnızca genişliği merak eden widget'ınız da yeniden çizilir. Tek bir özelliğe bağlanan erişimciler bu sorunu çözer:

@override
Widget build(BuildContext context) {
  final size = MediaQuery.sizeOf(context);
  final padding = MediaQuery.paddingOf(context);

  return Container(
    width: size.width * 0.8,
    margin: EdgeInsets.only(top: padding.top),
    color: Colors.blue,
    child: Center(child: Text('${size.width.toInt()} x ${size.height.toInt()}')),
  );
}

sizeOf yalnızca boyut değişince, paddingOf yalnızca güvenli alan değişince rebuild tetikler. Her özellik için bir karşılığı vardır: orientationOf, viewInsetsOf, devicePixelRatioOf, platformBrightnessOf, textScalerOf; hepsinin tam listesi Flutter'ın MediaQuery API dokümanında statik metotlar başlığı altında duruyor. Yeni kodda MediaQuery.of(context) yazmak için bir sebep kalmadı.

Hangi Özellik Ne Söyler?

Bir telefon penceresinde size tüm alanı, padding üst ve alt sistem şeritlerini, viewInsets ise klavyenin kapladığı alanı gösterir

Özellik Anlamı Tipik kullanım
size Pencerenin mantıksal piksel cinsinden boyutu Breakpoint kararları, yüzde hesabı
padding Çentik, durum çubuğu, home göstergesi gibi sistem alanları SafeArea bunu kullanır
viewInsets Klavye gibi geçici olarak alanı kaplayan sistem arayüzü Klavyeye göre alt boşluk
orientation Genişlik yüksekten büyükse landscape Yatayda farklı düzen
textScaler Kullanıcının sistemdeki metin büyütme ayarı Erişilebilirlik testi, sabit yükseklikleri ölçekleme
devicePixelRatio Mantıksal piksel başına fiziksel piksel Uygun çözünürlükte görsel seçmek
platformBrightness Sistemin açık/koyu tema tercihi Genelde ThemeMode.system yeterlidir

Tablodaki her alanın tam tanımını MediaQueryData sınıfının API dokümanında bulabilirsiniz. Dikkat edilmesi gereken bir nokta: size ekranı değil pencereyi verir. Telefonda ikisi aynıdır ama masaüstünde, web'de ve tablet bölünmüş ekranında pencere ekrandan küçüktür. Kararlarınızı "ekran" diye değil "pencere" diye düşünmek yanılgıları azaltır.

Yüzdeyle Boyutlama ve Alternatifi

Genişliğin yüzde 80'i kadar bir kart için size.width * 0.8 yazmak çalışır, ama bu kart bir yan panelin içine taşındığında oran panelin değil pencerenin yüzde 80'i olarak kalır. Üst widget'a göre oran istiyorsanız FractionallySizedBox bunu hesap yapmadan halleder:

Center(
  child: FractionallySizedBox(
    widthFactor: 0.8,
    child: Card(child: Padding(padding: EdgeInsets.all(16), child: Text('İçerik'))),
  ),
)

Kural olarak: "pencerenin yüzdesi" gerçekten isteniyorsa MediaQuery, "üst widget'ın yüzdesi" isteniyorsa FractionallySizedBox. Çoğu zaman ikincisi kastedilir.

MediaQuery mi LayoutBuilder mı?

İkisi farklı soruyu yanıtlar. MediaQuery.sizeOf "pencere ne kadar büyük?" der; LayoutBuilder "bu widget'a ne kadar yer verildi?" der. Bir ayar ekranını tablet'te sağ panelde açtığınızda pencere geniştir ama panele verilen genişlik dardır; LayoutBuilder doğru sonucu, MediaQuery yanlış sonucu verir.

Pratik ayrım şöyle: uygulama iskeletiyle ilgili kararlar (alt bar mı yan ray mı, tek sütun mu çift sütun mu) pencere boyutuna göre alınır, MediaQuery uygundur. Yeniden kullanılabilir bir bileşenin kendi içindeki düzen (kart yatay mı dikey mi) verilen alana göre alınır, LayoutBuilder uygundur. Ayrıntılı örnekleri ve breakpoint düzenini LayoutBuilder ile responsive tasarım yazısında bulabilirsiniz.

Metin Ölçeği

Kullanıcı sistemde metni büyüttüğünde Text widget'ı bunu zaten uygular; sizin fontSize ile çarpmanız gerekmez. MediaQuery.textScalerOf(context) iki durumda gerekir: metinle birlikte büyümesi gereken sabit yükseklikler ve büyümeyi sınırlama.

final scaler = MediaQuery.textScalerOf(context);
final rowHeight = scaler.scale(48); // 48 mantıksal piksel, metin ölçeğiyle büyür

// Belirli bir alt ağaçta büyütmeyi 1.3 ile sınırlamak:
MediaQuery.withClampedTextScaling(
  maxScaleFactor: 1.3,
  child: const PriceTag(),
)

Eski textScaleFactor sayısal bir çarpandı ve kullanımdan kaldırıldı; TextScaler sınıfının API dokümanının anlattığı gibi TextScaler doğrusal olmayan ölçeklemeyi de destekler, bu yüzden değeri bir sayı gibi çarpmak yerine scale() çağırın.

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

MediaQuery şu işlerde doğru araçtır: pencere genişliğine göre gezinme yapısını seçmek, güvenli alanı elle hesaba katmak gereken özel çizimler, klavye yüksekliğine göre alt boşluk vermek, erişilebilirlik için metin ölçeğini okumak, yöne göre tamamen farklı bir sayfa düzeni kurmak.

Şu durumlarda başka bir araç daha iyi:

  • Bileşenin kendisine verilen alan önemliyse LayoutBuilder; yalnızca yön gerekiyorsa OrientationBuilder (o da üst widget'ın kısıtlarına bakar).
  • Üst widget'ın yüzdesi için FractionallySizedBox; kardeşler arasında oran için Expanded. Bu ikisinin tarifleri Expanded, Flexible ve Spacer ile layout kontrolü yazısında var.
  • Güvenli alan için elle padding hesaplamak yerine SafeArea. Klavye için elle boşluk yerine Scaffold'un varsayılan resizeToAvoidBottomInset: true davranışı.
  • Font boyutunu genişlikle çarpmak yerine Theme.of(context).textTheme ve birkaç breakpoint. Yüzdeyle büyüyen yazı tablette devasa, küçük telefonda okunmaz olur ve kullanıcının erişilebilirlik ayarını ezer.

Sık Yapılan Hatalar

1. initState içinde MediaQuery okumak

Belirti: dependOnInheritedWidgetOfExactType<MediaQuery>() or dependOnInheritedElement() was called before _MyPageState.initState() completed. initState çalışırken widget henüz ağaca bağlı değildir. Değeri build içinde okuyun; bir kez hesaplanacaksa didChangeDependencies doğru yerdir.

2. fontSize'ı textScaleFactor ile çarpmak

Belirti: Kullanıcı sistemde metni yüzde 150 büyüttüğünde yazılar iki kat büyür, düzen dağılır. Text ölçeği zaten uyguluyor; fontSize: 16 * factor yazmak çift ölçekleme demektir. Çarpmayı kaldırın; sabit yükseklikleri ölçeklemek istiyorsanız textScalerOf(context).scale(...) kullanın.

3. Scaffold içinde klavye için elle padding vermek

Belirti: Klavye açıldığında formun altında klavye yüksekliğinin iki katı boşluk kalır. Scaffold gövdeyi klavyeye göre zaten küçültür; üstüne padding: EdgeInsets.only(bottom: viewInsets.bottom) eklemek boşluğu ikiye katlar. Ya Scaffold'a bırakın ya da resizeToAvoidBottomInset: false yapıp boşluğu kendiniz yönetin; ikisi birden değil.

4. Yan panel içindeki widget'ta pencere genişliğine bakmak

Belirti: Tablette sağ panelde açılan kart, pencere geniş olduğu için "geniş" düzeni seçer ve taşar. Widget'a verilen alan pencereden bağımsızdır. Kararı LayoutBuilder'ın constraints.maxWidth değerine göre verin.

Mini Senaryo: Genişliğe Göre Gezinme

Bir not uygulaması düşünün. Telefonda altta NavigationBar, tablette ve masaüstünde solda NavigationRail olsun; içerik ise her iki durumda da güvenli alanın içinde kalsın:

class HomeShell extends StatefulWidget {
  const HomeShell({super.key});

  @override
  State<HomeShell> createState() => _HomeShellState();
}

class _HomeShellState extends State<HomeShell> {
  int _index = 0;

  static const _destinations = [
    (icon: Icons.notes, label: 'Notlar'),
    (icon: Icons.search, label: 'Ara'),
    (icon: Icons.settings, label: 'Ayarlar'),
  ];

  @override
  Widget build(BuildContext context) {
    final isWide = MediaQuery.sizeOf(context).width >= 600;
    final body = SafeArea(child: NotesPage(index: _index));

    if (isWide) {
      return Scaffold(
        body: Row(
          children: [
            NavigationRail(
              selectedIndex: _index,
              onDestinationSelected: (i) => setState(() => _index = i),
              labelType: NavigationRailLabelType.all,
              destinations: [
                for (final d in _destinations)
                  NavigationRailDestination(icon: Icon(d.icon), label: Text(d.label)),
              ],
            ),
            const VerticalDivider(width: 1),
            Expanded(child: body),
          ],
        ),
      );
    }

    return Scaffold(
      body: body,
      bottomNavigationBar: NavigationBar(
        selectedIndex: _index,
        onDestinationSelected: (i) => setState(() => _index = i),
        destinations: [
          for (final d in _destinations)
            NavigationDestination(icon: Icon(d.icon), label: d.label),
        ],
      ),
    );
  }
}

Karar pencere genişliğine göre alındığı için MediaQuery.sizeOf doğru araç; bu bir iskelet kararı, bir bileşen kararı değil. sizeOf kullanıldığı için klavye açıldığında bu widget yeniden çizilmez; yalnızca pencere döndüğünde ya da masaüstünde yeniden boyutlandığında çalışır. Güvenli alanı SafeArea hallediyor, paddingOf ile elle hesap yok. NotesPage'in kendi içindeki düzen (notların tek mi iki sütun mu olduğu) ise pencereye değil ona verilen alana bağlı; orada LayoutBuilder kullanmak gerekir. İki gezinme widget'ının seçimi hakkında NavigationBar ve BottomNavigationBar karşılaştırması ayrıntı veriyor.

Sık Sorulan Sorular

MediaQuery.of ile MediaQuery.sizeOf arasındaki fark nedir?

MediaQuery.of(context) widget'ı MediaQuery'deki her değişikliğe bağlar; klavye açılınca bile rebuild olur. MediaQuery.sizeOf(context) yalnızca boyut değiştiğinde rebuild tetikler. Aynı mantıkla paddingOf, viewInsetsOf ve textScalerOf da vardır.

MediaQuery mi LayoutBuilder mı kullanmalıyım?

Pencere boyutuna göre iskelet kararı alıyorsanız (alt bar mı yan ray mı) MediaQuery. Bir bileşenin kendisine verilen alana göre düzen kuruyorsanız LayoutBuilder. Yeniden kullanılabilir bileşenlerde neredeyse her zaman ikincisi doğrudur.

Font boyutunu ekran genişliğine göre ölçeklemeli miyim?

Hayır. Text kullanıcının sistem metin ayarını zaten uygular; genişlikle çarpmak tablette devasa yazı üretir ve erişilebilirlik ayarını bozar. Birkaç breakpoint ile textTheme stillerini değiştirmek yeterlidir.

Klavye açıldığında içerik neden taşıyor?

Scaffold gövdeyi varsayılan olarak klavyeye göre küçültür; taşma oluyorsa gövde kaydırılabilir değildir. Formu SingleChildScrollView veya ListView içine alın. viewInsets ile elle boşluk vermek yalnızca resizeToAvoidBottomInset: false yaptığınızda gerekir.

Yorumlar