↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı

4 dk okuma #flutter
Dizi · 56/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 7 başlık
  1. Ne Zaman CustomPainter Yetmez?
  2. Üç Katman — Kimin İşi Ne?
  3. En Basit Örnek — Sabit Bir Kenar Boşluğu Ekleyen RenderObject
  4. Daha İleri Bir Örnek — Çoklu Çocuk, Özel Yerleşim (Dairesel Layout)
  5. Pratik Gerçek — Ne Sıklıkla Buna İhtiyaç Duyarsın?
  6. 🎯 Bu Dersten Çıkarılması Gerekenler
  7. 📝 Ödevler

Bölüm 8 Konu 38'de CustomPainter ile çizim katmanına doğrudan erişmeyi öğrenmiştik — ama CustomPainter'ın bir sınırı vardı: sana verilen bir alana (size) çizim yapıyordun, o alanın nasıl hesaplandığına (layout) karışamıyordun. Bu derste, Bölüm 1 Konu 1'den beri zihinsel model olarak taşıdığımız Widget → Element → RenderObject mimarisinin en alt katmanına, kendi layout mantığını yazabileceğin yere iniyoruz.

Ne Zaman CustomPainter Yetmez?

CustomPainter, "bana verilen dikdörtgen alana ne çizeceğim" sorusuna cevap verir. Ama şu soruları cevaplayamaz:

  • Bu widget'ın boyutu ne olmalı, ebeveyninden gelen constraint'lere göre?
  • Birden fazla çocuk widget'ı, standart Row/Column/Stack'in sunmadığı özel bir düzende (örneğin dairesel, spiral, ya da veri odaklı bir algoritmaya göre) nasıl yerleştiririm?

Bu sorular, Bölüm 2 Konu 8'de öğrendiğimiz "constraints go down, sizes go up" ilkesinin bizzat kendisini yazmayı gerektirir — işte bunun için RenderObject'e doğrudan ineriz.

Üç Katman — Kimin İşi Ne?

metin
Widget       → "Ne istiyorum" (değişmez, hafif, her build()'de yeniden oluşturulur)
Element      → Widget ile RenderObject arasındaki "yaşayan" köprü (durumu taşır)
RenderObject → "Nasıl olacak" (layout hesaplar, çizer — PAHALI, elden geldiğince yeniden kullanılır)

Bölüm 1'den beri bildiğin bu üçlüyü, şimdi elle yazacağız. CustomPainter (Konu 38), aslında arka planda senin için bir RenderObject (RenderCustomPaint) oluşturuyordu — bu ders, o "arka planı" sana gösteriyor.

En Basit Örnek — Sabit Bir Kenar Boşluğu Ekleyen RenderObject

Flutter'ın kendi Padding widget'ının basitleştirilmiş bir versiyonunu elle yazarak başlayalım — amaç Padding'i yeniden icat etmek değil, mekanizmayı görmek:

dart
class OzelKenarBoslugu extends SingleChildRenderObjectWidget {
  final double miktar;

  const OzelKenarBoslugu({super.key, required this.miktar, super.child});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return _OzelKenarBosluguRenderObject(miktar: miktar);
  }

  @override
  void updateRenderObject(BuildContext context, _OzelKenarBosluguRenderObject renderObject) {
    renderObject.miktar = miktar; // widget güncellendiğinde RenderObject'i güncelle
  }
}

class _OzelKenarBosluguRenderObject extends RenderShiftedBox {
  double _miktar;
  double get miktar => _miktar;
  set miktar(double deger) {
    if (_miktar == deger) return;
    _miktar = deger;
    markNeedsLayout(); // DEĞERİ DEĞİŞTİ — yeniden layout hesapla!
  }

  _OzelKenarBosluguRenderObject({required double miktar, RenderBox? child})
      : _miktar = miktar,
        super(child);

  @override
  void performLayout() {
    if (child == null) {
      size = constraints.smallest;
      return;
    }

    // Çocuğa, kendi boyutumuzdan kenar boşluğu kadar KÜÇÜLTÜLMÜŞ constraint ver
    final cocukConstraints = constraints.deflate(EdgeInsets.all(miktar));
    child!.layout(cocukConstraints, parentUsesSize: true);

    // Bizim boyutumuz = çocuğun boyutu + kenar boşlukları
    size = constraints.constrain(
      Size(child!.size.width + miktar * 2, child!.size.height + miktar * 2),
    );

    // Çocuğu, kenar boşluğu kadar İÇERİ kaydır
    (child!.parentData as BoxParentData).offset = Offset(miktar, miktar);
  }
}

Parça parça inceleyelim:

SingleChildRenderObjectWidget — Dart Bölüm 7'de öğrendiğimiz extends; Flutter'ın, tek bir çocuğu olan ve bu çocuğun layout'unu özelleştiren widget'lar için sunduğu hazır bir temel sınıf (Padding, Align, Center gibi widget'lar hep bunu kullanır).

createRenderObject(context) — widget ilk kez oluşturulduğunda çağrılır; gerçek RenderObject'i üretir. Bu, Bölüm 6 Konu 24'te Provider'ın create: parametresiyle (bir kere oluştur, sonra yeniden kullan) kavramsal olarak aynı fikir.

updateRenderObject(context, renderObject) — widget yeniden build() edildiğinde (yeni bir miktar değeriyle bile), Flutter yeni bir RenderObject oluşturmaz — var olanı günceller. Bu, RenderObject'in neden pahalı ve yeniden kullanılabilir olduğunu gösteren en somut örnek.

markNeedsLayout() — bu satır, tüm Flutter'ın reaktivitesinin temelidir. Bölüm 2 Konu 6'da öğrendiğimiz setState()'in, en dipte ne yaptığını şimdi görüyorsun: bir değer değiştiğinde, markNeedsLayout() (ya da sadece görünümü etkiliyorsa markNeedsPaint()) çağrılır — bu, Flutter'a "bu RenderObject'i bir sonraki karede yeniden hesapla" der. setState(), aslında çok daha üst seviyede, bu mekanizmayı senin için tetikleyen bir kolaylık.

performLayout() — asıl layout mantığının yazıldığı yer. constraints (Bölüm 2 Konu 8'de öğrendiğimiz kavram, burada somut bir BoxConstraints nesnesi olarak elinde), ebeveynden gelen sınırlar. constraints.deflate(...), bu sınırları küçültüp çocuğa veriyor — "constraints go down" ilkesinin birebir kod karşılığı.

child!.layout(cocukConstraints, parentUsesSize: true) — çocuğun kendi performLayout()'unu tetikler ve sonucunu (child!.size) okumana izin verir — "sizes go up" ilkesinin birebir kod karşılığı.

(child!.parentData as BoxParentData).offset = ... — çocuğun, bizim içimizde nereye konumlandırılacağını belirler. parentData, Dart Bölüm 7'de öğrendiğimiz tip cast (as) kullanımıyla, her RenderObject'in ebeveyni tarafından eklenen ekstra bilgiyi (burada konum) taşıdığı bir alan.

Daha İleri Bir Örnek — Çoklu Çocuk, Özel Yerleşim (Dairesel Layout)

dart
class DaireselYerlesim extends MultiChildRenderObjectWidget {
  DaireselYerlesim({super.key, required List<Widget> children}) : super(children: children);

  @override
  RenderObject createRenderObject(BuildContext context) => _DaireselYerlesimRenderBox();
}

class _DaireselYerlesimParentData extends ContainerBoxParentData<RenderBox> {}

class _DaireselYerlesimRenderBox extends RenderBox
    with ContainerRenderObjectMixin<RenderBox, _DaireselYerlesimParentData>,
         RenderBoxContainerDefaultsMixin<RenderBox, _DaireselYerlesimParentData> {

  @override
  void setupParentData(RenderBox child) {
    child.parentData = _DaireselYerlesimParentData();
  }

  @override
  void performLayout() {
    size = constraints.biggest; // mevcut TÜM alanı kullan
    final merkez = Offset(size.width / 2, size.height / 2);
    final yaricap = size.width / 3;

    int i = 0;
    int sayim = childCount;
    RenderBox? cocuk = firstChild;

    while (cocuk != null) {
      cocuk.layout(const BoxConstraints.tightFor(width: 50, height: 50), parentUsesSize: true);

      final aci = (2 * 3.14159 * i) / sayim; // Dart Bölüm 3 — matematik operatörleri
      final konum = Offset(
        merkez.dx + yaricap * cos(aci) - 25,
        merkez.dy + yaricap * sin(aci) - 25,
      );

      (cocuk.parentData as _DaireselYerlesimParentData).offset = konum;
      cocuk = (cocuk.parentData as _DaireselYerlesimParentData).nextSibling;
      i++;
    }
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    defaultPaint(context, offset); // tüm çocukları normal şekilde çiz
  }
}

Bu örnek neyi gösteriyor? Stack, Row, Column'ın kapsamadığı bir yerleşim algoritmasını (çocukları bir dairenin çevresine eşit aralıklarla yerleştirme), tamamen kendi matematiğinle tanımlayabiliyorsun. ContainerRenderObjectMixin, Dart Bölüm 7'de öğrendiğimiz mixin kavramının burada birden fazla çocuğu bir bağlı liste gibi (firstChild, nextSibling) yönetmeni sağlayan hazır bir altyapısı.

Pratik Gerçek — Ne Sıklıkla Buna İhtiyaç Duyarsın?

Dürüst olmak gerekirse: çok nadiren. Flutter'ın hazır layout widget'ları (Row, Column, Stack, Wrap, Flow, CustomMultiChildLayout) çoğu senaryoyu karşılar. CustomMultiChildLayout (bu dersin gösterdiği ham RenderObject yazımından çok daha kolay, delegate tabanlı bir API), çoğu "özel yerleşim" ihtiyacını elle RenderObject yazmadan çözer. Bu dersin asıl değeri, teknik değil kavramsaldır: artık Row'un, Padding'in, Stack'in arka planda gerçekte ne yaptığını biliyorsun — bir performans sorununu (Bölüm 10) araştırırken ya da bir paketin (Bölüm 12 Konu 58) kaynak koduna baktığında, gördüğün şey artık sihir değil.


🎯 Bu Dersten Çıkarılması Gerekenler

  • RenderObject, Widget → Element → RenderObject üçlüsünün en alt, en pahalı katmanıdır — createRenderObject/updateRenderObject ile widget'lar bunu oluşturur/günceller, kendisi yeniden kullanılır.
  • markNeedsLayout()/markNeedsPaint(), Flutter'ın reaktivitesinin temelidir — setState(), bunları senin yerine çağıran üst seviye bir kolaylıktır.
  • performLayout() içinde, constraints (ebeveynden gelen) ile çocuğa küçültülmüş/değiştirilmiş constraint'ler verilir (constraints go down), çocuğun size'ı okunur (sizes go up), ve parentData.offset ile konumu belirlenir.
  • Çoklu çocuklu özel yerleşimler, ContainerRenderObjectMixin gibi hazır mixin'lerle, çocukları bağlı liste (firstChild/nextSibling) olarak yönetir.
  • Pratikte, elle RenderObject yazmak nadiren gerekir — CustomMultiChildLayout gibi daha kolay API'ler çoğu ihtiyacı karşılar; bu dersin asıl değeri, Flutter'ın iç mekanizmasını anlamaktır.

📝 Ödevler

  • [ ] OzelKenarBoslugu örneğini kendi projende dene, miktar'ı bir setState() ile değiştirip animasyonsuz ama anlık güncellendiğini gözlemle.
  • [ ] DaireselYerlesim örneğini, farklı sayıda çocukla (3, 5, 8) test et, yaricap değerini değiştirerek layout'un nasıl tepki verdiğini gözlemle.
  • [ ] Flutter'ın kaynak kodunda (GitHub) RenderPadding sınıfını bul, bu dersteki OzelKenarBoslugu ile karşılaştır — gerçek implementasyonun hangi ekstra detayları (örn. RTL/LTR metin yönü desteği) ele aldığını not al.
  • [ ] Kendi cümlelerinle, "markNeedsLayout() ile markNeedsPaint() arasındaki farkın ne olabileceğini" (hangi durumda sadece çizimin, hangi durumda boyut/konumun yeniden hesaplanması gerektiğini düşünerek) açıkla.

Sıradaki konu: Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)