Flutter
Flutter Accessibility (a11y): Herkes İçin Kullanılabilir Uygulamalar
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: '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: '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: 4),
Text('İ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(
'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: 'Sipariş öğesi: Espresso',
customSemanticsActions: {
CustomSemanticsAction(label: 'Siparişten kaldır'): _removeItem,
CustomSemanticsAction(label: 'Miktarı artır'): _increaseQty,
CustomSemanticsAction(label: '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:
- AA — normal metin için minimum 4.5:1, büyük metin (18 pt+ veya 14 pt+ kalın) için 3:1 kontrast oranı.
- AAA — normal metin için 7:1, büyük metin için 4.5:1.
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 gibi araçları kullanın. Flutter'da erişilebilirlik denetçi katmanını etkinleştirebilirsiniz:
MaterialApp(
showSemanticsDebugger: true,
// ...
)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: 8),
Text(isOnline ? 'Çevrimiçi' : 'Ç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.
// Kötü: ölçeklenmiş metni kırpacak sabit yükseklik
SizedBox(
height: 48,
child: Text('Ayarlar'),
)
// İyi: içeriğin büyümesine izin verin
Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Text('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(1.0).clamp(1.0, 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
- Ayarlar > Erişilebilirlik > VoiceOver yolunu izleyip etkinleştirin.
- Geliştirme sırasında iOS Simülatöründe Cmd + F5 kısayolunu kullanarak VoiceOver'ı açıp kapatın.
- Sonraki öğeye geçmek için sağa kaydırın. Etkinleştirmek için çift dokunun.
- Eksik etiketleri, kafa karıştıran sıralamayı ve gereksiz duyuruları dinleyin.
Android — TalkBack
- Ayarlar > Erişilebilirlik > TalkBack yolunu izleyip etkinleştirin.
- Android Emülatöründe, TalkBack'i programatik olarak etkinleştirmek için adb shell settings put secure enabled_accessibility_services com.google.android.marvin.talkback/.TalkBackService komutunu kullanın.
- Gezinmek için sağa kaydırın. Etkinleştirmek için çift dokunun.
- Okuma sırasına dikkat edin — görsel olarak yukarıdan aşağıya, soldan sağa akışı takip etmelidir.
Nelere Dikkat Etmeli?
- Eksik etiketler: Ekran okuyucu açıklama olmadan sadece "düğme" der.
- Gereksiz etiketler: Okuyucu "düğme, Favori düğmesi" der — rol iki kez duyurulur.
- Yanlış öğe gruplaması: İlişkili bilgiler birçok durağa bölünür, gezinmeyi yavaşlatır.
- Bozuk odak sırası: Okuyucu ekranın beklenmedik bölümlerine atlar.
- Eksik durum değişikliği duyuruları: Bir anahtar değiştirildikten sonra yeni durum duyurulmaz.
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 'package:flutter/semantics.dart';
SemanticsService.announce('Ürün sepete eklendi. Sepet toplamı: 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(1),
child: TextField(decoration: InputDecoration(labelText: 'E-posta')),
),
FocusTraversalOrder(
order: NumericFocusOrder(2),
child: TextField(decoration: InputDecoration(labelText: 'Şifre')),
),
FocusTraversalOrder(
order: NumericFocusOrder(3),
child: ElevatedButton(onPressed: _login, child: Text('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.
// Minimum dokunma hedefi boyutunu sağlayın
SizedBox(
width: 48,
height: 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:
- Dekoratif görsellerde ExcludeSemantics eksikliği. Her
Imagewidget'ı, siz açıkça hariç tutmadığınız sürece ekran okuyucu tarafından algılanır. Arka plan görselleri, ayırıcılar, marka filigranları — hepsini gizleyin. - Etiketsiz ikon düğmeleri.
tooltipolmayan birIconButton, yalnızca "düğme" olarak duyurulur. Her zamantooltipbelirleyin — Flutter bunu otomatik olarak semantik etiket olarak kullanır. - Öğeleri gizlemek için Opacity(opacity: 0) kullanımı. Widget semantik ağaçta kalmaya devam eder. Bunun yerine widget'ı ağaçtan tamamen kaldıran
Visibility(visible: false)veyaOffstagekullanın. - Metin ölçeklendirmeyi görmezden gelmek. Geliştiriciler yalnızca 1.0x'te test eder. 2.0x ölçeklendirme kullanan bir kullanıcı uygulamayı açtığında metinler taşar, düzenler bozulur ve kritik aksiyonlara erişilemez.
- Özel widget'larda tamamen eksik semantikler. Elle yapılmış bir toggle, çizilmiş bir grafik, hareketle kontrol edilen bir kart —
Semanticseklemediğiniz sürece bunlar yardımcı teknolojiler için görünmezdir. - Hata duyurusu olmayan formlar. Doğrulama başarısız olduğunda hata metni görsel olarak belirir ama asla duyurulmaz.
SemanticsService.announceile görsel hata göstergelerini birleştirin. - Etiketsiz metin alanları. Yalnızca
hintTextile oluşturulanTextField, kullanıcı yazmaya başladığında etiketini kaybeder. Her zamanInputDecoration(labelText: ...)kullanın. - Devre dışı bırakılamayan animasyonlar. Bazı kullanıcılar hareket tutarsızlığı yaşar.
MediaQuery.disableAnimationsdeğerine saygı gösterin ve azaltılmış hareket alternatifleri sunun.
Otomatik Test
Flutter, testlerde bazı erişilebilirlik sorunlarını yakalamak için araçlar sunar:
testWidgets('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:
- [ ] Tüm etkileşimli öğelerin semantik etiketleri var
- [ ] Dekoratif görseller semantik ağaçtan hariç tutulmuş
- [ ] Renk kontrastı WCAG AA'yı karşılıyor (metin için 4.5:1, büyük metin için 3:1)
- [ ] Renk, anlamın tek göstergesi olarak kullanılmıyor
- [ ] Arayüz 2.0x metin ölçek faktöründe düzgün çalışıyor
- [ ] Ekran okuyucu gezinme sırası görsel düzene uyuyor
- [ ] Modal ve diyaloglarda odak doğru şekilde tutuluyor
- [ ] Dokunma hedefleri en az 48 x 48 dp boyutunda
- [ ] Dinamik içerik değişiklikleri SemanticsService ile duyuruluyor
- [ ] Form hataları yalnızca görsel değil, sesli olarak da duyuruluyor
- [ ] Animasyonlar azaltılmış hareket ayarına saygı gösteriyor
- [ ] Otomatik erişilebilirlik testleri CI'da geçiyor
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ç