Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
Dizi · 38/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 9 başlık
- Neden CustomPainter? Hazır Widget'lar Yetmiyor mu?
- Widget → Element → RenderObject Hatırlatması
- Temel Yapı
- Dinamik Bir Örnek — Dairesel İlerleme Çubuğu
- AnimatedBuilder ile Birleştirme — Animasyonlu Bir Çizim
- CustomPaint'in child Parametresi
- Metin Çizmek — TextPainter
- 🎯 Bu Dersten Çıkarılması Gerekenler
- 📝 Ödevler
Şimdiye kadar hep hazır widget'ları (Container, Icon, Image) birleştirerek arayüz kurduk. Bu ders, Flutter'ın kendi çizim motoruna doğrudan erişmeni sağlıyor — bir dairesel ilerleme çubuğu, özel bir grafik, ya da hazır widget'larla yapamayacağın herhangi bir şekli çizebilmeni.
Neden CustomPainter? Hazır Widget'lar Yetmiyor mu?
Container, BoxDecoration, ClipPath gibi widget'larla çoğu basit şekli (dikdörtgen, daire, kenarlık) çizebilirsin. Ama şu durumlarda CustomPainter'a ihtiyaç duyarsın:
- Veriye dayalı özel grafikler (bir çizgi grafiği, bir pasta grafiği, bir dalga formu).
- Piksel seviyesinde kontrol gerektiren, hazır widget'larla ifade edilemeyen şekiller.
- Performans kritik çizimler — yüzlerce küçük şekli tek tek widget olarak oluşturmak yerine, hepsini tek bir
Canvas'a çizmek çok daha hızlıdır.
Widget → Element → RenderObject Hatırlatması
Bölüm 1 Konu 1'de öğrendiğimiz üç katmanlı mimariyi (Widget → Element → RenderObject) hatırlarsan — her widget, sonunda bir RenderObject'e dönüşür ve gerçek çizim, RenderObject.paint() metodunda, bir Canvas üzerinde gerçekleşir. CustomPainter, bu düşük seviye çizim katmanına, kendi RenderObject'ini yazmak zorunda kalmadan doğrudan erişmeni sağlayan bir kolaylık katmanıdır (Bölüm 12'de, RenderObject'i tamamen elle yazmayı göreceğiz — CustomPainter, o işin %90'ını basitleştirilmiş bir arayüzle sunar).
Temel Yapı
class DaireCizici extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
final boya = Paint()
..color = Colors.indigo
..style = PaintingStyle.fill;
final merkez = Offset(size.width / 2, size.height / 2);
final yaricap = size.width / 2;
canvas.drawCircle(merkez, yaricap, boya);
}
@override
bool shouldRepaint(covariant DaireCizici oldDelegate) {
return false; // statik bir çizim, hiç yeniden çizilmesine gerek yok
}
}CustomPaint(
size: const Size(200, 200),
painter: DaireCizici(),
)Parça parça inceleyelim:
extends CustomPainter — Dart Bölüm 7'de öğrendiğimiz extends; CustomPainter, Flutter'ın soyut bir sınıfıdır (Bölüm 7'de öğrendiğimiz gibi, paint/shouldRepaint zorunlu override edilecek metodlardır).
paint(Canvas canvas, Size size) — bu metod, her çizim gerektiğinde çağrılır. canvas, üzerine çizim yapacağın tuvaldir; size, CustomPaint widget'ının sana ayırdığı alanın boyutudur (Bölüm 2 Konu 8'de öğrendiğimiz constraint sistemini hatırla — CustomPaint, kendisine verilen constraint'lere göre bir boyut alır, paint() bu boyutu size parametresiyle sana bildirir).
Paint nesnesi — Dart Bölüm 6'da öğrendiğimiz class + cascade operatörü (..) kombinasyonu; Paint, nasıl çizeceğini (renk, dolgu mu çizgi mi, çizgi kalınlığı) tanımlar — kalemi/fırçayı seçmek gibi düşün.
canvas.drawCircle(merkez, yaricap, boya) — asıl çizim komutu. Canvas sınıfı, drawCircle, drawRect, drawLine, drawPath, drawArc gibi düzinelerce çizim metodu sunar.
shouldRepaint(covariant DaireCizici oldDelegate) — Bölüm 6 Konu 23'te InheritedWidget'ın updateShouldNotify'ını öğrenmiştik; bu, kavramsal olarak birebir aynı fikir. false döndürürsen, Flutter bu çizimi bir kere yapar ve bir daha (veri değişmediği sürece) tekrar çizmez — performans için kritik. covariant anahtar kelimesi, Dart Bölüm 7'de değinmediğimiz ama burada karşına çıkan bir detay: üst sınıftaki (CustomPainter) parametre tipi CustomPainter'dır, ama biz burada daha spesifik bir tip (DaireCizici) kullanmak istiyoruz — covariant, Dart'a "bu daraltmayı bilerek yapıyorum, tip güvenliğini burada gevşet" demenin yoludur.
Dinamik Bir Örnek — Dairesel İlerleme Çubuğu
class IlerlemeCizici extends CustomPainter {
final double ilerleme; // 0.0 - 1.0 arası (Konu 36'daki Animation.value gibi düşünebilirsin)
IlerlemeCizici({required this.ilerleme});
@override
void paint(Canvas canvas, Size size) {
final merkez = Offset(size.width / 2, size.height / 2);
final yaricap = size.width / 2;
// Arka plan halkası (gri, tam daire)
final arkaPlanBoyasi = Paint()
..color = Colors.grey[300]!
..style = PaintingStyle.stroke
..strokeWidth = 12;
canvas.drawCircle(merkez, yaricap, arkaPlanBoyasi);
// İlerleme yayı (mavi, sadece 'ilerleme' kadar)
final ilerlemeBoyasi = Paint()
..color = Colors.indigo
..style = PaintingStyle.stroke
..strokeWidth = 12
..strokeCap = StrokeCap.round;
final rect = Rect.fromCircle(center: merkez, radius: yaricap);
const baslangicAcisi = -3.14159 / 2; // saat 12 yönünden başla
final taramaAcisi = 2 * 3.14159 * ilerleme; // Dart Bölüm 3 — matematik operatörleri
canvas.drawArc(rect, baslangicAcisi, taramaAcisi, false, ilerlemeBoyasi);
}
@override
bool shouldRepaint(covariant IlerlemeCizici oldDelegate) {
return oldDelegate.ilerleme != ilerleme; // sadece değer değiştiğinde yeniden çiz
}
}Dikkat et: ilerleme, Dart Bölüm 6'da öğrendiğimiz constructor parametresi olarak veriliyor — bu, CustomPainter'ı her ilerleme değeri için yeniden oluşturarak kullanacağın anlamına gelir (tıpkı bir widget'ı yeniden build() etmek gibi). shouldRepaint, eski ve yeni ilerleme'yi karşılaştırarak, gereksiz yeniden çizimleri önlüyor.
AnimatedBuilder ile Birleştirme — Animasyonlu Bir Çizim
Konu 36'da öğrendiğimiz AnimatedBuilder'ı, CustomPainter ile birleştirmek çok yaygın bir kalıptır:
class DaireselIlerlemeCubugu extends StatefulWidget {
const DaireselIlerlemeCubugu({super.key});
@override
State<DaireselIlerlemeCubugu> createState() => _DaireselIlerlemeCubuguState();
}
class _DaireselIlerlemeCubuguState extends State<DaireselIlerlemeCubugu>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
size: const Size(150, 150),
painter: IlerlemeCizici(ilerleme: _controller.value),
);
},
);
}
}Burada, _controller.value (Konu 36'da öğrendiğimiz 0.0–1.0 arası ilerleme), her karede IlerlemeCizici'ye yeni bir ilerleme değeri olarak geçiliyor — bu da shouldRepaint'in true dönmesine ve ekranın her karede yeniden çizilmesine yol açıyor. Bu, tam olarak CustomPainter + AnimationController'ın birlikte çalışma deseni.
CustomPaint'in child Parametresi
CustomPaint(
size: const Size(200, 200),
painter: DaireCizici(),
child: const Center(child: Text('50%')), // çizimin ÜZERİNE bir widget koy
)CustomPaint, sadece çizim yapmakla kalmaz — child: parametresiyle, çizimin üzerine normal widget'lar (metin, ikon) da koyabilirsin. Bu, örneğin bir dairesel ilerleme çubuğunun ortasına yüzde metni yazmak için çok kullanışlıdır.
Metin Çizmek — TextPainter
Canvas'a doğrudan Text widget'ı koyamazsın (widget değil, düşük seviye bir çizim yüzeyi) — metin çizmek için TextPainter kullanılır:
void _metinCiz(Canvas canvas, Size size) {
final textPainter = TextPainter(
text: const TextSpan(
text: 'Merhaba',
style: TextStyle(color: Colors.black, fontSize: 16),
),
textDirection: TextDirection.ltr,
);
textPainter.layout(); // önce boyutunu hesapla
textPainter.paint(canvas, Offset(size.width / 2 - textPainter.width / 2, 10));
}layout() çağrısı, Bölüm 2 Konu 8'de öğrendiğimiz "constraints go down, sizes go up" ilkesinin düşük seviyede bir yansımasıdır — metnin ne kadar yer kaplayacağını önce hesaplaman (layout()), sonra çizmen (paint()) gerekir.
🎯 Bu Dersten Çıkarılması Gerekenler
CustomPainter,RenderObject'inpaint()katmanına doğrudan erişim sağlar — hazır widget'larla ifade edilemeyen özel çizimler için kullanılır.paint(Canvas canvas, Size size), asıl çizimin yapıldığı metoddur;Paintnesnesi rengi/stili,Canvasmetodları (drawCircle,drawArc,drawPathvb.) şekli belirler.shouldRepaint,InheritedWidget'ınupdateShouldNotify'ına benzer — gereksiz yeniden çizimleri önler.CustomPainter+AnimationController+AnimatedBuilder, animasyonlu özel çizimlerin standart birleşimidir.CustomPaint'inchild:parametresi, çizimin üzerine normal widget koymanı sağlar.- Metin çizmek için
Textwidget'ı değil,TextPainter(layout()+paint()) kullanılır.
📝 Ödevler
- [ ]
CustomPainterile basit bir üçgen çiz (Path+drawPathkullanarak —moveTo/lineTo/closemetodlarını araştır). - [ ] Dairesel ilerleme çubuğu örneğini,
AnimationControllerile 0'dan 1'e animasyonlu şekilde dolduracak hale getir. - [ ]
CustomPaint'inchild:parametresiyle, ilerleme çubuğunun ortasına o anki yüzdeyi gösteren birTextkoy. - [ ]
TextPainterkullanarak, birCanvasüzerine ortalanmış bir başlık metni çiz. - [ ] Kendi cümlelerinle, "
shouldRepaint'i her zamantruedöndürseydik ne olurdu" sorusunu, performans açısından açıkla.
Sıradaki konu: Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu (Bölüm 8'in Son Konusu)