Flutter Accessibility (a11y): Herkes İçin Kullanılabilir Uygulamalar
# Flutter Erişilebilirlik (a11y): Herkes İçin Uygulama Geliştirmek
Erişilebilirlik, yayına almadan önce eklenen kozmetik bir iyileştirme değil — ürün kalitesinin ta kendisidir. Flutter'da a11y'den bahsettiğimizde, ekran okuyucu kullanan, az gören, motor beceri kısıtlılığı olan ve daha birçok farklı ihtiyaca sahip kullanıcının uygulamayı algılayabilmesini, gezinebilmesini ve etkileşim kurabilmesini kastediyoruz.
Bu rehberde Flutter'ın sunduğu araçları, hemen uygulayabileceğiniz kod örneklerini ve denetimlerde en sık karşılaştığım hataları paylaşıyorum.
Erişilebilirlik Neden Önemli?
Dünya nüfusunun yaklaşık %15'i bir tür engelle yaşıyor. Etik boyutun ötesinde, erişilebilir uygulamalar daha geniş bir kitleye ulaşır ve ADA (ABD), EAA (AB), BITV (Almanya) gibi yasal gereklilikleri karşılar. Mühendislik açısından bakarsak, erişilebilir kod genellikle daha iyi yapılandırılmış koddur: net semantikler widget'ları test etmeyi, yeniden düzenlemeyi ve ekibe yeni katılan geliştiricilerin kodu anlamasını kolaylaştırır.
Semantik Ağaç (Semantics Tree)
Flutter kendi piksellerini çizdiği için işletim sistemi, UIKit veya Android Views'ta olduğu gibi native görünümlerden anlam çıkaramaz. Bunun yerine Flutter, kullanıcı arayüzünü yardımcı teknolojilere tanımlayan paralel bir Semantik ağaç tutar.
Anlam taşıyan her widget, bu ağaca bir düğüm eklemelidir. `Text`, `ElevatedButton`, `Checkbox` gibi yerleşik widget'lar bunu otomatik yapar. Özel widget'larınız için semantiği kendiniz sağlamanız gerekir.
Semantics Widget'ı
`Semantics`, ana aracınızdır. Anlam taşıyan ancak yerleşik semantiği olmayan widget'ları sarmalayın:
Semantics(
label: class="code-string">'Mevcut kullanıcının profil fotoğrafı',
image: true,
child: CircleAvatar(
backgroundImage: NetworkImage(user.avatarUrl),
),
)Etkileşimli özel widget'lar için kullanılabilir aksiyonları tanımlayın:
Semantics(
label: class="code-string">'Bu makaleyi favorilere ekle',
button: true,
onTap: () => _toggleFavorite(),
child: GestureDetector(
onTap: _toggleFavorite,
child: Icon(
isFavorite ? Icons.star : Icons.star_border,
color: isFavorite ? Colors.amber : Colors.grey,
),
),
)MergeSemantics
Birden fazla widget tek bir mantıksal birim oluşturduğunda, ekran okuyucu bunları tek bir öğe olarak duyurmalıdır. Grubu `MergeSemantics` ile sarmalayın:
MergeSemantics(
child: Row(
children: [
Icon(Icons.location_on),
SizedBox(width: class="code-number">4),
Text(class="code-string">'İstanbul, Türkiye'),
],
),
)Bu sarmalama olmadan TalkBack, "location_on" ve ardından "İstanbul, Türkiye" şeklinde iki ayrı öğe olarak duyurur ki bu kafa karıştırıcıdır.
ExcludeSemantics
Bilgi değeri taşımayan dekoratif öğeler, ekran okuyucu çıktısını karmaşıklaştırmamaları için semantik ağaçtan gizlenmelidir:
ExcludeSemantics(
child: Image.asset(
class="code-string">'assets/dekoratif_ayirici.png',
),
)Aynı etkiyi `Semantics(excludeSemantics: true, child: ...)` ile de sağlayabilirsiniz, ancak `ExcludeSemantics` widget ağacında daha okunabilir durur.
Özel Semantik Aksiyonlar
Karmaşık widget'lar için — kaydırarak kapatılabilen kartlar, sıfırdan oluşturulmuş slider'lar, sürükle-bırak kutucukları — özel semantik aksiyonlar tanımlayabilirsiniz:
Semantics(
label: class="code-string">'Sipariş öğesi: Espresso',
customSemanticsActions: {
CustomSemanticsAction(label: class="code-string">'Siparişten kaldır'): _removeItem,
CustomSemanticsAction(label: class="code-string">'Miktarı artır'): _increaseQty,
CustomSemanticsAction(label: class="code-string">'Miktarı azalt'): _decreaseQty,
},
child: _buildOrderTile(),
)Bu sayede ekran okuyucu kullanıcıları, gören kullanıcıların kaydırma hareketleri veya dokunma hedefleriyle elde ettiği işlevlerin aynısına sahip olur.
Renk Kontrastı ve Tipografi
WCAG Kontrast Gereksinimleri
Web İçerik Erişilebilirlik Kılavuzları (WCAG 2.1), mobil için geçerli iki uyumluluk seviyesi tanımlar:
Erişilebilirlik denetimi yaptığım uygulamalarda en sık karşılaştığım sorun, ikincil metin ve yer tutucu (placeholder) ipuçlarında yetersiz kontrasttır. Beyaz üzerine açık gri en yaygın hatalı kombinasyondur.
Kontrastı Pratikte Kontrol Etme
Tasarım aşamasında [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) gibi araçları kullanın. Flutter'da erişilebilirlik denetçi katmanını etkinleştirebilirsiniz:
MaterialApp(
showSemanticsDebugger: true,
class=class="code-string">"code-comment">// ...
)Bu, çizilen arayüzü semantik ağacın görselleştirmesiyle değiştirir; eksik etiketler ve yapısal sorunlar anında görünür hale gelir.
Yalnızca Renge Güvenmeyin
Durumu belirtmek için rengi tek gösterge olarak asla kullanmayın. Kırmızı/yeşil durum noktası, protanopi (renk körlüğü) olan biri için anlamsızdır. Rengi her zaman bir şekil, ikon veya metin etiketiyle eşleştirin:
Row(
children: [
Icon(
isOnline ? Icons.check_circle : Icons.cancel,
color: isOnline ? Colors.green : Colors.red,
),
SizedBox(width: class="code-number">8),
Text(isOnline ? class="code-string">'Çevrimiçi' : class="code-string">'Çevrimdışı'),
],
)Metin Ölçeklendirme ve Dinamik Yazı Tipi
Kullanıcılar okunabilirlik için sistem yazı tipi boyutunu artırabilir. Flutter, `Text` widget'larında `MediaQuery.textScaleFactor` değerini varsayılan olarak dikkate alır, ancak özel düzenler metin büyüdüğünde sıklıkla bozulur.
Ölçeklenmiş Metin İçin Tasarım
Uygulamanızı 1.0, 1.5 ve 2.0 ölçek faktörlerinde test edin. Sabit yükseklik, tek satır varsayımı veya dar dolgu kullanan düzenler taşacaktır.
class=class="code-string">"code-comment">// Kötü: ölçeklenmiş metni kırpacak sabit yükseklik
SizedBox(
height: class="code-number">48,
child: Text(class="code-string">'Ayarlar'),
)
class=class="code-string">"code-comment">// İyi: içeriğin büyümesine izin verin
Padding(
padding: EdgeInsets.symmetric(vertical: class="code-number">12),
child: Text(class="code-string">'Ayarlar'),
)Kullanıcı Tercihlerine Saygı Gösterme
Metin ölçeklendirmesini sınırlamanız kesinlikle gerekiyorsa (örneğin küçük bir rozet widget'ında), bunu açıkça yapın ve nedenini belgeleyin:
MediaQuery(
data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(
MediaQuery.of(context).textScaler.scale(class="code-number">1.0).clamp(class="code-number">1.0, class="code-number">1.3),
),
),
child: _BadgeWidget(),
)Erişilebilirlik denetimi yaptığım uygulamalarda, metin ölçeklendirme sorunlarının çoğunun basit etiketlerde değil, veri tabloları, alt navigasyon çubukları ve input süslemeleri gibi karmaşık düzenlerde ortaya çıktığını görüyorum. Bu alanları her zaman elle doğrulayın.
Ekran Okuyucu Testi — Pratik Rehber
Otomatik araçlar erişilebilirlik sorunlarının yalnızca yaklaşık %30'unu yakalar. Ekran okuyucu ile elle test vazgeçilmezdir.
iOS — VoiceOver
Android — TalkBack
Nelere Dikkat Etmeli?
Dinamik Değişiklikleri Duyurma
İçerik bir gezinme olayı olmadan güncellendiğinde — snackbar belirir, sayaç artar, form doğrulama yapar — değişikliği duyurmak için `SemanticsService` kullanın:
import class="code-string">'package:flutter/semantics.dart';
SemanticsService.announce(class="code-string">'Ürün sepete eklendi. Sepet toplamı: class="code-number">3 ürün.', TextDirection.ltr);Odak Yönetimi ve Klavye Navigasyonu
Mantıksal Odak Sırası
Odak, ekranda tahmin edilebilir bir sırayla ilerlemelidir. Flutter genellikle widget ağacı sırasını takip eder, ancak overlay'ler, diyaloglar ve karmaşık düzenler bu sırayı bozabilir.
Navigasyonu kontrol etmek için `FocusTraversalGroup` ve `FocusTraversalOrder` kullanın:
FocusTraversalGroup(
policy: OrderedTraversalPolicy(),
child: Column(
children: [
FocusTraversalOrder(
order: NumericFocusOrder(class="code-number">1),
child: TextField(decoration: InputDecoration(labelText: class="code-string">'E-posta')),
),
FocusTraversalOrder(
order: NumericFocusOrder(class="code-number">2),
child: TextField(decoration: InputDecoration(labelText: class="code-string">'Şifre')),
),
FocusTraversalOrder(
order: NumericFocusOrder(class="code-number">3),
child: ElevatedButton(onPressed: _login, child: Text(class="code-string">'Giriş Yap')),
),
],
),
)Modal'larda Odak Kapanı
Bir diyalog veya alt sayfa açıldığında, odak içeride tutulmalıdır; böylece kullanıcı yanlışlıkla arka plan içeriğiyle etkileşime giremez. Flutter'ın `showDialog` ve `showModalBottomSheet` fonksiyonları bunu yönetir, ancak özel overlay'ler yönetmeyebilir. Her zaman doğrulayın.
Dokunma Hedefi Boyutu
WCAG 2.5.5, minimum dokunma hedefi olarak 44 x 44 CSS pikseli önerir. Material Design kılavuzları 48 x 48 dp önerir. Küçük dokunma hedefleri, motor beceri kısıtlılığı olan kullanıcılar için büyük bir engeldir.
class=class="code-string">"code-comment">// Minimum dokunma hedefi boyutunu sağlayın
SizedBox(
width: class="code-number">48,
height: class="code-number">48,
child: IconButton(
icon: Icon(Icons.close),
onPressed: _dismiss,
),
)`IconButton` varsayılan olarak 48 dp kısıtlamasını uygular, ancak `GestureDetector` tabanlı özel widget'lar genellikle bunu yapmaz.
Sık Yapılan Erişilebilirlik Hataları
Onlarca Flutter uygulamasının denetimini yaptıktan sonra, en sık karşılaştığım sorunları paylaşmak istiyorum:
Otomatik Test
Flutter, testlerde bazı erişilebilirlik sorunlarını yakalamak için araçlar sunar:
testWidgets(class="code-string">'ana ekran erişilebilirlik kılavuzlarını geçiyor', (tester) async {
await tester.pumpWidget(MyApp());
final handle = tester.ensureSemantics();
await expectLater(tester, meetsGuideline(androidTapTargetGuideline));
await expectLater(tester, meetsGuideline(iOSTapTargetGuideline));
await expectLater(tester, meetsGuideline(labeledTapTargetGuideline));
await expectLater(tester, meetsGuideline(textContrastGuideline));
handle.dispose();
});Bu kontrolleri CI pipeline'ınıza entegre edin, böylece gerilemeler kullanıcılara ulaşmadan yakalanır.
Erişilebilirlik Kontrol Listesi
Her sürümden önce bu listeyi kullanın:
Sonuç
Erişilebilir uygulamalar, yalnızca bir alt küme için değil, tüm kullanıcılar için daha iyi deneyimler yaratır. Yukarıdaki uygulamaların birçoğu — net semantikler, tahmin edilebilir odak, ölçeklenebilir düzenler — kullanıcının becerisinden bağımsız olarak kodunuzu daha temiz, ürününüzü daha sağlam hale getirir. Erişilebilirlik bir kez gönderdiğiniz bir özellik değil, her commit'te sürdürdüğünüz bir disiplindir.
İsterseniz kritik kullanıcı akışlarınız için erişilebilirlik denetimi gerçekleştirebilirim — uygulamanızı herkes için çalışır hale getirelim.
İlgili Makaleler
Flutter Performans Optimizasyonu: Kapsamlı Rehber
Flutter uygulamanızın performansını sistematik olarak artırın. Rebuild optimizasyonu, bellek yönetimi, lazy loading ve profiling tekniklerini öğrenin.
Flutter Form Validation: Kullanılabilir ve Güvenli Formlar
Flutter formlarında doğrulama, hata mesajı tasarımı ve kullanıcı deneyimi için pratik yöntemleri öğrenin.
Flutter Localization (i18n): Çok Dilli Uygulama Geliştirme
Flutter'da çok dilli yapı kurun. ARB dosyaları, locale yönetimi ve çeviri süreçlerini ölçeklenebilir hale getirin.
Flutter Projeniz mi Var?
iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.
İletişime Geç