↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine

5 dk okuma #flutter
Dizi · 60/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 12 başlık
  1. Neden Önemli? Sadece "Ekstra Özellik" Değil
  2. Ekran Okuyucular — TalkBack ve VoiceOver
  3. Semantics Widget'ı — Görünmez Ama Kritik Katman
  4. excludeSemantics ve mergeSemantics
  5. Dokunma Hedefi Boyutu — 48x48 Kuralı
  6. Renk Kontrastı — WCAG Standardı
  7. Metin Ölçeklendirme — textScaler ile Uyum
  8. Klavye ve Odak (Focus) Navigasyonu
  9. Test Etme — flutter_test ile Otomatik Denetim
  10. Semantics Debugger — Görsel Doğrulama
  11. 🎯 Bu Dersten Çıkarılması Gerekenler
  12. 📝 Ödevler

Bölüm 2'den beri onlarca widget yazdık — Text, Icon, ElevatedButton, GestureDetector... Ama hiçbirinde şu soruyu sormadık: görme engelli bir kullanıcı, bu ekranı nasıl kullanır? Bu ders, Flutter'ın erişilebilirlik (a11y) desteğini — ekran okuyucular, dokunma hedefleri, kontrast ve klavye navigasyonu — işliyor.

Neden Önemli? Sadece "Ekstra Özellik" Değil

Erişilebilirlik, isteğe bağlı bir cila değil — birçok ülkede (ve app store'ların kendi kurallarında) yasal bir zorunluluktur (örneğin ABD'de ADA, AB'de EAA). Ayrıca görme engelli olmayan kullanıcılar için de fayda sağlar: güneş ışığında ekranı zor gören biri, geçici olarak kolu kırılmış biri (tek elle kullanım), ya da yaşlanan bir kullanıcı — hepsi erişilebilirlik iyileştirmelerinden fayda görür.

Ekran Okuyucular — TalkBack ve VoiceOver

Bölüm 9'da Android/iOS platform farklarını öğrenmiştik — her ikisinin de kendi yerleşik ekran okuyucusu vardır:

  • Android: TalkBack
  • iOS: VoiceOver

Bunlar açıkken, kullanıcı ekrana dokunduğunda (tıklamadan), o widget'ın ne olduğu sesli olarak okunur; çift dokunma ile widget'ı "aktive eder" (tıklamış gibi).

Semantics Widget'ı — Görünmez Ama Kritik Katman

Flutter'ın widget ağacının yanında, görünmeyen ikinci bir ağaç daha vardır: Semantics ağacı. Her widget, render ettiği pikselin yanında, ekran okuyucuya "ben neyim, ne işe yararım" bilgisini de taşır — Bölüm 12 Konu 56'da öğrendiğimiz RenderObject ağacına paralel bir yapı.

dart
// Çoğu built-in widget bunu OTOMATİK sağlar:
IconButton(
  icon: Icon(Icons.favorite),
  onPressed: () {},
  tooltip: 'Favorilere ekle',  // ekran okuyucu BUNU okur!
)

tooltip, sadece masaüstünde fare ile bekletince görünen bir ipucu değildir — TalkBack/VoiceOver için de birincil açıklama kaynağıdır. Ama bir Icon widget'ını tooltip olmadan, çıplak kullanırsan:

dart
GestureDetector(
  onTap: () {},
  child: Icon(Icons.favorite),  // ekran okuyucu SADECE "buton" der, NE işe yaradığını söylemez!
)

Bu durumda, Semantics widget'ıyla elle açıklama eklemen gerekir:

dart
Semantics(
  label: 'Favorilere ekle',   // ekran okuyucunun OKUYACAĞI metin
  button: true,               // bunun bir BUTON olduğunu belirtir
  child: GestureDetector(
    onTap: () {},
    child: Icon(Icons.favorite),
  ),
)

Semantics, Bölüm 6 Konu 23'te öğrendiğimiz InheritedWidget gibi ağaçta yukarıdan aşağıya bilgi taşıyan bir widget'tır — ama taşıdığı şey veri değil, "bu widget ne anlama geliyor" bilgisidir.

excludeSemantics ve mergeSemantics

dart
// Dekoratif bir ikon (bilgi taşımıyor, sadece görsel) — ekran okuyucuya GEREKSIZ gürültü:
ExcludeSemantics(
  child: Icon(Icons.star, color: Colors.amber),  // "yıldız ikonu" diye okunmasın
)

// Birden fazla widget'ı TEK bir semantik birim olarak birleştirme:
MergeSemantics(
  child: Row(
    children: [
      Icon(Icons.star),
      Text('4.5 (128 değerlendirme)'),  // TalkBack bunu TEK seferde okur: "yıldız, 4.5, 128 değerlendirme"
    ],
  ),
)

ExcludeSemantics, Bölüm 10 Konu 49'da öğrendiğimiz performans optimizasyonuna benzer bir disiplin taşır — gereksiz olanı budayarak, kullanıcı deneyimini (bu sefer performansı değil, dinleme deneyimini) iyileştirirsin. Bir ekranda çok fazla gereksiz semantik bilgi, ekran okuyucu kullanıcısı için tıpkı çok fazla gereksiz rebuildin performansı boğması gibi, deneyimi boğar.

Dokunma Hedefi Boyutu — 48x48 Kuralı

Hem Android hem iOS tasarım rehberleri, dokunulabilir bir öğenin en az 48x48 (Android) / 44x44 (iOS) mantıksal piksel olmasını önerir — küçük parmak motor becerisi olan kullanıcılar (ya da sadece kalın parmaklı biri) için.

dart
// KÖTÜ: küçük bir ikon, dokunma alanı da küçük
IconButton(
  icon: Icon(Icons.close, size: 16),
  onPressed: () {},
)

// İYİ: IconButton'ın kendi iç dolgusu (padding), dokunma alanını
// otomatik olarak Material rehberindeki minimuma çıkarır
IconButton(
  icon: Icon(Icons.close, size: 16),
  onPressed: () {},
  // IconButton, varsayılan olarak en az 48x48'lik bir dokunma alanı SAĞLAR,
  // görünen ikon küçük olsa bile
)

Dikkat edilmesi gereken tuzak: Bir GestureDetector'ı doğrudan küçük bir Icon'un etrafına sararsan (yukarıdaki IconButton yerine), dokunma alanı sadece ikonun görsel boyutu kadar olur — bu durumda Padding veya sabit bir SizedBox ile dokunma alanını elle büyütmen gerekir.

Renk Kontrastı — WCAG Standardı

WCAG (Web Content Accessibility Guidelines), metin ile arka plan arasındaki kontrast oranı için asgari standartlar tanımlar — normal metin için en az 4.5:1, büyük metin için en az 3:1.

dart
// KÖTÜ: açık gri metin, beyaz arka plan üzerinde — kontrast oranı çok düşük
Text('Önemli bilgi', style: TextStyle(color: Color(0xFFCCCCCC)))

// İYİ: yeterli kontrast
Text('Önemli bilgi', style: TextStyle(color: Color(0xFF333333)))

Bölüm 4'te öğrendiğimiz Material 3 ColorScheme.fromSeed (Konu 10'da güncellediğimiz pattern'i hatırla), Google'ın kendi renk algoritmalarına göre, onSurface/onPrimary gibi renkleri otomatik olarak yeterli kontrastla üretir — bu yüzden elle renk seçmek yerine Theme.of(context).colorScheme'den renk almak, hem tutarlılık hem erişilebilirlik açısından avantajlıdır.

Metin Ölçeklendirme — textScaler ile Uyum

Konu 17'de (Bölüm 4), MediaQuery.textScalerOf(context)'i (eski textScaleFactor'ün yerine geçen API) görmüştük — kullanıcı, sistem ayarlarından yazı boyutunu büyütebilir (görme zorluğu çeken kullanıcılar için kritik bir özellik). Bunun erişilebilirlik boyutu: uygulamanın, kullanıcı yazı boyutunu %200'e kadar büyüttüğünde bile bozulmadan çalışması gerekir.

dart
// KÖTÜ: sabit yükseklikli bir Container içine Text koymak, büyütülmüş
// yazı taştığında RenderFlex overflow hatasına yol açar
Container(
  height: 40,
  child: Text('Uzun bir buton metni', style: TextStyle(fontSize: 16)),
)

// İYİ: esnek boyutlandırma, taşmayı ÖNLER
Container(
  padding: EdgeInsets.symmetric(vertical: 12),
  child: Text('Uzun bir buton metni', style: TextStyle(fontSize: 16)),
)

Pratik test yöntemi: Cihazının sistem ayarlarından yazı boyutunu en büyük değere getirip, uygulamanı öyle gez — Bölüm 10 Konu 45'te öğrendiğimiz test disiplininin, erişilebilirlik için elle yapılan bir versiyonu.

Klavye ve Odak (Focus) Navigasyonu

Masaüstü/web'de (Konu 55'i hatırla), kullanıcılar Tab tuşuyla widget'lar arasında gezinir — bu, FocusNode ve FocusTraversalGroup ile yönetilir:

dart
FocusTraversalGroup(
  policy: OrderedTraversalPolicy(),  // Tab sırasını ELLE kontrol etmek istersen
  child: Column(
    children: [
      TextField(
        focusNode: FocusNode(),
        decoration: InputDecoration(labelText: 'E-posta'),
      ),
      TextField(
        focusNode: FocusNode(),
        decoration: InputDecoration(labelText: 'Şifre'),
      ),
    ],
  ),
)

Flutter, varsayılan olarak widget ağacındaki sırayı Tab sırası olarak kullanır — çoğu durumda elle müdahale gerekmez; ama karmaşık, çok sütunlu layout'larda (örn. bir Row içinde yan yana iki Column), görsel sıra ile mantıksal Tab sırası farklılaşabilir, ki o zaman FocusTraversalGroup/OrderedTraversalPolicy devreye girer.

Test Etme — flutter_test ile Otomatik Denetim

Bölüm 10 Konu 45'te öğrendiğimiz widget test altyapısı, erişilebilirlik denetimi için de kullanılabilir:

dart
testWidgets('erişilebilirlik denetimi', (tester) async {
  await tester.pumpWidget(MyApp());

  final SemanticsHandle handle = tester.ensureSemantics();

  // dokunma hedefi boyutu, metin kontrastı gibi kuralları OTOMATİK denetler:
  await expectLater(tester, meetsGuideline(textContrastGuideline));
  await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
  await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));

  handle.dispose();
});

meetsGuideline, Bölüm 10 Konu 45'teki expect/matcher mantığının erişilebilirliğe özelleşmiş hali — bu testleri, Konu 46'daki CI pipeline'ına ekleyerek, her PR'da otomatik olarak erişilebilirlik regresyonlarını yakalayabilirsin.

Semantics Debugger — Görsel Doğrulama

dart
MaterialApp(
  showSemanticsDebugger: true,  // GELİŞTİRME sırasında, semantik ağacı EKRANDA görselleştirir
  home: MyHomePage(),
)

Bu, Bölüm 10 Konu 48'de öğrendiğimiz DevTools'un Widget Inspector'ına benzer bir fikir — görünmeyen bir yapıyı (semantik ağaç), geliştirme sırasında görünür kılarak hata ayıklamayı kolaylaştırır. (Üretim build'inde mutlaka false olmalı — sadece geliştirme aracıdır.)


🎯 Bu Dersten Çıkarılması Gerekenler

  • Erişilebilirlik, yasal bir zorunluluk olmasının yanında görme/motor zorluğu çeken kullanıcılara ve geçici kısıtlılık yaşayan herkese fayda sağlar.
  • Semantics widget'ı, Bölüm 6 Konu 23'teki InheritedWidget'a benzer şekilde, widget ağacına paralel bir anlam ağacı taşır; çoğu built-in widget bunu otomatik sağlar, çıplak GestureDetector+Icon gibi kombinasyonlarda elle eklenmesi gerekir.
  • ExcludeSemantics/MergeSemantics, gereksiz/parçalı semantik bilgiyi budayarak ekran okuyucu deneyimini iyileştirir.
  • Dokunma hedefleri en az 48x48 (Android) / 44x44 (iOS) mantıksal piksel olmalı; IconButton bunu otomatik sağlar, çıplak GestureDetector sağlamaz.
  • Renk kontrastı WCAG standartlarına (4.5:1 normal metin) uymalı — Material 3 ColorScheme.fromSeed bunu büyük ölçüde otomatikleştirir.
  • Layout'lar, textScaler ile büyütülmüş yazı boyutlarında taşmadan çalışmalı.
  • FocusTraversalGroup/OrderedTraversalPolicy, karmaşık layout'larda Tab sırasını kontrol eder.
  • meetsGuideline matcher'ları ile erişilebilirlik denetimi, Bölüm 10'daki test/CI disiplinine entegre edilebilir; showSemanticsDebugger: true geliştirme sırasında semantik ağacı görselleştirir.

📝 Ödevler

  • [ ] Cihazında TalkBack (Android) veya VoiceOver (iOS)'u aç, bu tutorial boyunca yazdığın bir uygulamayı sadece dokunarak ve ekran okuyucuyu dinleyerek kullanmayı dene — hangi butonların "ne olduğu belirsiz" okunduğunu not al.
  • [ ] O uygulamadaki en az iki çıplak Icon+GestureDetector kombinasyonuna Semantics(label: ..., button: true) ekle.
  • [ ] Sistem ayarlarından yazı boyutunu maksimuma çıkar, uygulamanı gez, taşan (overflow) bir ekran bulup düzelt.
  • [ ] meetsGuideline(textContrastGuideline) kullanan bir widget testi yaz ve çalıştır.
  • [ ] Kendi cümlelerinle, "Semantics ağacının, Bölüm 6'daki InheritedWidget ile neden benzer bir yapısal fikri paylaştığını" açıkla.

Sıradaki konu: Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n) — Flutter tutorial'ının son konusu!