Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
Dizi · 56/61 Flutter Türkçe Tutorial
- Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
- Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
- Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
- Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
- Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
- Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
- Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
- Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
- Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
- Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
- Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
- Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
- Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
- Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
- Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
- Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
- Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
- Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
- Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
- Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
- Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
- Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
- Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
- Bölüm 6 — Konu 24: Provider Paketi
- Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
- Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
- Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
- Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
- Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
- Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
- Bölüm 7 — Konu 31: Deep Linking
- Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
- Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
- Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
- Bölüm 8 — Konu 35: Implicit Animasyonlar
- Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
- Bölüm 8 — Konu 37: Hero Animasyonları
- Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
- Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
- Bölüm 9 — Konu 40: Platform Channels
- Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
- Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
- Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
- Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
- Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
- Bölüm 10 — Konu 46: CI/CD
- Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
- Bölüm 10 — Konu 48: DevTools Profiling
- Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
- Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
- Bölüm 11 — Konu 51: Supabase Alternatifi
- Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
- Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
- Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
- Bölüm 12 — Konu 55: Flutter Web / Desktop
- Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
- Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
- Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
- Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
- Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
- Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
İçindekiler 7 başlık
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?
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:
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)
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/updateRenderObjectile 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ğunsize'ı okunur (sizes go up), veparentData.offsetile konumu belirlenir.- Çoklu çocuklu özel yerleşimler,
ContainerRenderObjectMixingibi hazır mixin'lerle, çocukları bağlı liste (firstChild/nextSibling) olarak yönetir. - Pratikte, elle
RenderObjectyazmak nadiren gerekir —CustomMultiChildLayoutgibi 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'ı birsetState()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,yaricapdeğerini değiştirerek layout'un nasıl tepki verdiğini gözlemle. - [ ] Flutter'ın kaynak kodunda (GitHub)
RenderPaddingsınıfını bul, bu derstekiOzelKenarBosluguile karşılaştır — gerçek implementasyonun hangi ekstra detayları (örn.RTL/LTRmetin yönü desteği) ele aldığını not al. - [ ] Kendi cümlelerinle, "
markNeedsLayout()ilemarkNeedsPaint()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)