↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)

4 dk okuma #flutter
Dizi · 17/61 Flutter Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
  3. Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
  4. Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
  5. Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
  6. Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
  7. Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
  8. Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
  9. Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
  10. Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
  11. Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
  12. Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
  13. Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
  14. Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
  15. Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
  16. Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
  17. Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
  18. Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
  19. Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
  20. Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
  21. Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
  22. Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
  23. Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
  24. Bölüm 6 — Konu 24: Provider Paketi
  25. Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
  26. Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
  27. Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
  28. Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
  29. Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
  30. Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
  31. Bölüm 7 — Konu 31: Deep Linking
  32. Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
  33. Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
  34. Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
  35. Bölüm 8 — Konu 35: Implicit Animasyonlar
  36. Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
  37. Bölüm 8 — Konu 37: Hero Animasyonları
  38. Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
  39. Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
  40. Bölüm 9 — Konu 40: Platform Channels
  41. Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
  42. Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
  43. Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
  44. Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
  45. Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
  46. Bölüm 10 — Konu 46: CI/CD
  47. Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
  48. Bölüm 10 — Konu 48: DevTools Profiling
  49. Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
  50. Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
  51. Bölüm 11 — Konu 51: Supabase Alternatifi
  52. Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
  53. Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
  54. Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
  55. Bölüm 12 — Konu 55: Flutter Web / Desktop
  56. Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
  57. Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
  58. Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
  59. Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
  60. Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
  61. Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
Dizinin sayfası →
İçindekiler 11 başlık
  1. MediaQuery — Cihaz/Ekran Hakkında Bilgi Alma
  2. Basit Bir Breakpoint Stratejisi
  3. LayoutBuilder — Ebeveynin Boyutuna Göre Karar Verme
  4. OrientationBuilder — Yön Değişikliklerine Tepki Verme
  5. Responsive vs Adaptive — Terminoloji Farkı
  6. Pratik Bir Örnek — İkisini Birleştirmek
  7. MediaQuery Performans Notu — Kısa Bir Önizleme
  8. 🎯 Bu Dersten Çıkarılması Gerekenler
  9. 📝 Ödevler
  10. 🎮 Mini Uygulama — Uyarlanabilir Ürün Kataloğu
  11. 🏁 Bölüm 4 Tamamlandı!

Bölüm 4'ün son konusundayız. Flutter uygulamaları, telefon, tablet, masaüstü, web gibi çok farklı ekran boyutlarında çalışabilir — bu derste, arayüzünü bu farklılıklara uyarlamayı öğreneceğiz.

MediaQuery — Cihaz/Ekran Hakkında Bilgi Alma

dart
Widget build(BuildContext context) {
  final ekranBoyutu = MediaQuery.of(context).size;
  final genislik = ekranBoyutu.width;
  final yukseklik = ekranBoyutu.height;

  return Text('Ekran: ${genislik.toInt()} x ${yukseklik.toInt()}');
}

MediaQuery.of(context), Bölüm 4 Konu 16'da öğrendiğimiz Theme.of(context) ile aynı mekanizmayı kullanır — widget ağacında en yakın MediaQueryData'yı bulur (bu da bir InheritedWidget'a dayanır, Bölüm 8'de tam göreceğiz). MediaQuery, ekran boyutu dışında da birçok bilgi sağlar:

dart
MediaQuery.of(context).size.width          // ekran genişliği
MediaQuery.of(context).orientation         // yatay/dikey (Orientation.portrait/landscape)
MediaQuery.of(context).padding.top         // üstteki "çentik"/durum çubuğu boşluğu
MediaQuery.textScalerOf(context)           // kullanıcının sistem yazı boyutu ayarı

Not: Eskiden (Flutter 3.12 öncesi) bu son satır için MediaQuery.of(context).textScaleFactor (bir double) kullanılırdı. Bu artık deprecated — çünkü tek bir çarpanla ölçeklendirme, erişilebilirlik açısından her yazı boyutu için doğru sonuç vermiyordu. Güncel API TextScaler nesnesi döndürür ve bir font boyutunu ölçeklendirmek için MediaQuery.textScalerOf(context).scale(16) gibi kullanılır (16, ölçeklenecek orijinal font boyutu).

Basit Bir Breakpoint Stratejisi

Breakpoint, "bu genişlikten sonra düzeni değiştir" kararının alındığı eşik değerdir. Web geliştirmede yaygın olan bu kavram, Flutter'da da uygulanabilir:

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

  @override
  Widget build(BuildContext context) {
    final genislik = MediaQuery.of(context).size.width;

    if (genislik < 600) {
      return TekSutunGorunum(); // telefon
    } else if (genislik < 1024) {
      return IkiSutunGorunum(); // tablet
    } else {
      return UcSutunGorunum(); // masaüstü/geniş ekran
    }
  }
}

Bu, Dart Bölüm 3'te öğrendiğimiz if/else if yapısının (ya da bir switch expression'ın — Dart Bölüm 3 Konu 12'yi hatırla) doğrudan bir uygulaması — ekran genişliğine göre farklı widget ağaçları döndürüyoruz.

LayoutBuilder — Ebeveynin Boyutuna Göre Karar Verme

MediaQuery, tüm ekranın boyutunu verir. Ama bazen, sadece belirli bir widget'ın kendisine ayrılan alanın boyutuna göre karar vermen gerekir (örneğin bir widget, ekranın yarısını kaplıyorsa, "ekran genişliği" değil, "kendisine ayrılan alan" önemlidir):

dart
LayoutBuilder(
  builder: (BuildContext context, BoxConstraints constraints) {
    if (constraints.maxWidth < 300) {
      return Icon(Icons.star); // dar alan — sadece ikon
    } else {
      return Row(
        children: [Icon(Icons.star), Text('Favorilerim')],
      );
    }
  },
)

BoxConstraints constraints — Bölüm 2 Konu 8'de öğrendiğimiz constraint sistemini hatırlıyor musun? "Constraints go down" demiştik — işte LayoutBuilder, bu kısıtlamayı doğrudan sana verir! constraints.maxWidth, "bu widget'a ebeveyninden gelen maksimum genişlik kısıtlaması nedir" sorusunun cevabıdır. Bu, Bölüm 3 Konu 11'de öğrendiğimiz builder pattern'in (context ve bir parametre alan anonim fonksiyon) bir başka uygulaması — burada index yerine constraints alıyoruz.

MediaQuery ile LayoutBuilder arasındaki fark:

MediaQuery LayoutBuilder
Neyin boyutunu verir? Tüm ekranın boyutu Bu widget'a ayrılan alanın boyutu
Ne zaman kullanılır? Genel, uygulama çapında kararlar Yerel, "bu widget kendi alanında ne kadar yer var" kararları

OrientationBuilder — Yön Değişikliklerine Tepki Verme

dart
OrientationBuilder(
  builder: (context, orientation) {
    return GridView.count(
      crossAxisCount: orientation == Orientation.portrait ? 2 : 4,
      children: List.generate(8, (i) => Container(color: Colors.blue[100 * (i % 8 + 1)])),
    );
  },
)

OrientationBuilder, cihaz dikey (portrait) mi yatay (landscape) mi tutulduğuna göre farklı bir yapı kurmanı sağlar — burada, dikey modda 2 sütun, yatay modda 4 sütunlu bir GridView (Bölüm 3 Konu 11'i hatırla) oluşturuyoruz.

Responsive vs Adaptive — Terminoloji Farkı

Bu iki terim sık karıştırılır:

  • Responsive (duyarlı) tasarım: Aynı widget ağacı, akıcı (fluid) bir şekilde farklı boyutlara uyum sağlar (Expanded/Flexible ile oranlar otomatik ayarlanır — Bölüm 2 Konu 9'u hatırla).
  • Adaptive (uyarlanabilir) tasarım: Farklı ekran boyutları için tamamen farklı widget ağaçları/düzenler kullanılır (yukarıdaki ResponsiveEkran örneğindeki gibi — telefon için ayrı, tablet için ayrı bir widget).

Gerçek uygulamalarda, genelde ikisi birlikte kullanılır: büyük yapısal kararlar (kaç sütun, hangi navigasyon deseni) adaptive; her boyut içindeki ince ayarlar (Expanded oranları) responsive olur.

Pratik Bir Örnek — İkisini Birleştirmek

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

  @override
  Widget build(BuildContext context) {
    final genislik = MediaQuery.of(context).size.width;
    final genisEkranMi = genislik >= 600; // ADAPTIVE karar

    return Scaffold(
      body: Row(
        children: [
          Expanded(
            flex: genisEkranMi ? 2 : 1, // RESPONSIVE oran
            child: UrunListesi(),
          ),
          if (genisEkranMi) // sadece geniş ekranda göster — ADAPTIVE
            Expanded(
              flex: 3,
              child: UrunDetayPaneli(),
            ),
        ],
      ),
    );
  }
}

Burada, Dart Bölüm 4 Konu 20'de öğrendiğimiz collection if kalıbı (if (genisEkranMi) Expanded(...)) — dar ekranda UrunDetayPaneli hiç oluşturulmuyor (telefon kullanıcısı, muhtemelen bir sonraki ekranda detayı görür), geniş ekranda ise yan yana iki panel gösteriliyor. Bu, tablet/masaüstü uygulamalarında (özellikle "master-detail" — liste + detay yan yana deseni) çok yaygın bir kalıptır.

MediaQuery Performans Notu — Kısa Bir Önizleme

Bölüm 9'da (performans) derinlemesine işleyeceğiz, ama şimdiden bilmen faydalı: MediaQuery.of(context) çağırdığında, o widget, ekran boyutu her değiştiğinde (örneğin cihaz döndürüldüğünde) otomatik olarak yeniden oluşturulur (rebuild) — çünkü InheritedWidget mekanizması, "bu veriye bağımlı olan her widget'ı, veri değiştiğinde güncelle" prensibiyle çalışır (Bölüm 8'de tam işleyeceğiz). Bu, genelde istediğin davranıştır (ekran boyutu değiştiğinde arayüzün güncellenmesini istersin), ama gereksiz yere geniş kapsamda MediaQuery kullanmak, performans açısından dikkat gerektirebilir.


🎯 Bu Dersten Çıkarılması Gerekenler

  • MediaQuery.of(context), tüm ekranın boyutu, yönü ve diğer cihaz bilgilerine erişim sağlar.
  • LayoutBuilder, widget'a ebeveyninden gelen kısıtlamayı (BoxConstraints) doğrudan verir — Bölüm 2'de öğrendiğimiz constraint sisteminin canlı bir uygulaması.
  • OrientationBuilder, cihazın dikey/yatay durumuna göre farklı düzen kurmayı sağlar.
  • Responsive tasarım (akıcı oranlar) ile adaptive tasarım (tamamen farklı yapılar), genelde birlikte kullanılır.
  • Breakpoint stratejisi, genişliğe göre if/else if ile farklı widget ağaçları seçmenin temel yoludur.

📝 Ödevler

  • [ ] MediaQuery ile ekran genişliğini al, 600px altında/üstünde farklı bir mesaj gösteren basit bir widget yaz.
  • [ ] LayoutBuilder kullanarak, ekran genişliği 600px altındaysa tek sütun, üstündeyse iki sütun gösteren bir grid yapısı kur.
  • [ ] OrientationBuilder ile, dikey/yatay moda göre farklı sayıda sütunlu bir GridView oluştur (bir emulator'de döndürerek test et).
  • [ ] Konu 17'deki "UrunEkrani" örneğine benzer, dar ekranda tek panel, geniş ekranda iki panel gösteren (collection if kullanarak) bir master-detail düzeni yap.
  • [ ] Kendi cümlelerinle, "responsive" ile "adaptive" tasarım arasındaki farkı, kendi örneklerinle açıkla.

🎮 Mini Uygulama — Uyarlanabilir Ürün Kataloğu

Bölüm 4'te öğrendiğin form, tema ve responsive tasarım konularını birleştiren bir mini proje:

Gereksinimler:

  • ThemeData ile özel bir tema tanımla (renk, buton stili).
  • MediaQuery/LayoutBuilder ile, dar ekranda 2 sütunlu, geniş ekranda 4 sütunlu bir ürün ızgarası (GridView.builder, Bölüm 3 Konu 11'i hatırla) göster.
  • Her ürün kartına tıklanınca (Bölüm 3 Konu 14'ü hatırla — Navigator.push), bir "ürün detay" ekranına geç.
  • Detay ekranında, Form + TextFormField ile basit bir "yorum yaz" formu ekle (validasyonlu — boş yorum gönderilemesin).
  • AppBar'da bir karanlık mod switch'i ekle (Bölüm 4 Konu 16'yı hatırla).

Bu proje, Bölüm 2, 3 ve 4'ün tamamını kapsayan, gerçekçi bir uygulama parçasıdır.


🏁 Bölüm 4 Tamamlandı!

Form yönetimini, validasyonu, tema sistemini ve responsive/adaptive tasarımı öğrendin. Artık kullanıcıdan veri alabilen, tutarlı stillere sahip ve farklı ekran boyutlarına uyum sağlayan uygulamalar yazabiliyorsun.

Sıradaki durak Bölüm 5 — Asenkron Dünya & Veri: FutureBuilder, StreamBuilder, HTTP istekleri ve JSON serialization ile, uygulamanı gerçek bir API'ye bağlayacaksın — Dart Bölüm 10'da öğrendiğin Future/Stream bilgisinin Flutter'daki tam karşılığını göreceksin.