Kavramlar ve Jetpack Compose uygulaması
Android çerçevesi, erişilebilirlik ihtiyaçları olan kullanıcılara yardımcı olmak için uygulamalardaki içerikleri kullanıcılara sunabilen ve uygulamaları onlar adına çalıştırabilen bir erişilebilirlik hizmeti oluşturmanıza olanak tanır.
Android, aşağıdakiler de dahil olmak üzere çeşitli sistem erişilebilirlik hizmetleri sunar:
- TalkBack: Az gören veya görme engelli kullanıcılara yardımcı olur. İçeriği sentezlenmiş bir sesle duyurur ve kullanıcının hareketlerine yanıt olarak uygulamada işlemler gerçekleştirir.
- Anahtar Erişimi: Hareket engelli kişilere yardımcı olur. Etkileşimli öğeleri vurgular ve kullanıcının bir düğmeye basması üzerine işlemler gerçekleştirir. Cihazın yalnızca bir veya iki düğme kullanılarak kontrol edilmesine olanak tanır.
Erişilebilirlik ihtiyaçları olan kullanıcıların uygulamanızı başarılı bir şekilde kullanabilmesi için uygulamanızın, Uygulamaları daha erişilebilir hale getirme başlıklı makalede açıklanan yönergelerden yararlanan ve bu sayfada açıklanan en iyi uygulamalara uyması gerekir.
Etiket öğeleri
Uygulamanızdaki her etkileşimli kullanıcı arayüzü öğesi için kullanıcılara faydalı ve açıklayıcı etiketler sağlamanız önemlidir. Her etiket, belirli bir öğenin anlamını ve amacını açıklamalıdır. TalkBack gibi ekran okuyucular bu etiketleri kullanıcılara okuyabilir.
Çoğu durumda, bir kullanıcı arayüzü öğesinin açıklamasını, öğeyi içeren düzen kaynağı dosyasında belirtirsiniz. Genellikle, uygulamaları daha erişilebilir hale getirme kılavuzunda açıklandığı gibi, etiketleri contentDescription özelliğini kullanarak eklersiniz. Aşağıdaki bölümlerde açıklanan başka etiketleme teknikleri de vardır.
Düzenlenebilir öğeler
EditText nesneleri gibi düzenlenebilir öğeleri etiketlerken, bu öğenin kendisinde geçerli giriş örneği veren bir metin göstermek ve bu örnek metni ekran okuyuculara sunmak faydalıdır. Bu durumlarda, aşağıdaki snippet'te gösterildiği gibi android:hint özelliğini kullanabilirsiniz:
<!-- The hint text for en-US locale would be "Apartment, suite, or building". --> <EditText android:id="@+id/addressLine2" android:hint="@string/aptSuiteBuilding" ... />
Bu durumda, View nesnesinin android:labelFor özelliği, EditText öğesinin kimliğine ayarlanmalıdır. Daha fazla bilgi için aşağıdaki bölüme bakın.
Birinin diğerini tanımladığı öğe çiftleri
EditText öğesinin, kullanıcıların EditText öğesine ne girmesi gerektiğini açıklayan karşılık gelen bir View nesnesi olması yaygındır. Bu ilişkiyi, View nesnesinin android:labelFor özelliğini ayarlayarak belirtebilirsiniz.
Bu tür öğe çiftlerini etiketleme örneği aşağıdaki snippet'te gösterilmektedir:
<!-- Label text for en-US locale would be "Username:" --> <TextView android:id="@+id/usernameLabel" ... android:text="@string/username" android:labelFor="@+id/usernameEntry" /> <EditText android:id="@+id/usernameEntry" ... /> <!-- Label text for en-US locale would be "Password:" --> <TextView android:id="@+id/passwordLabel" ... android:text="@string/password android:labelFor="@+id/passwordEntry" /> <EditText android:id="@+id/passwordEntry" android:inputType="textPassword" ... />
Koleksiyondaki öğeler
Bir koleksiyonun öğelerine etiket eklerken her etiket benzersiz olmalıdır. Bu sayede, sistemin erişilebilirlik hizmetleri bir etiketi duyururken tam olarak tek bir ekran öğesine başvurabilir. Bu yazışma, kullanıcıların kullanıcı arayüzünde dolaşırken veya odağı daha önce keşfettikleri bir öğeye taşırken bilgilendirilmesini sağlar.
Özellikle, yeniden kullanılan düzenlerdeki öğelere (ör. RecyclerView nesneleri) ek metin veya bağlamsal bilgiler ekleyerek her alt öğenin benzersiz şekilde tanımlanmasını sağlayın.
Bunu yapmak için aşağıdaki kod snippet'inde gösterildiği gibi, içerik açıklamasını bağdaştırıcı uygulamanızın bir parçası olarak ayarlayın:
Kotlin
data class MovieRating(val title: String, val starRating: Integer) class MyMovieRatingsAdapter(private val myData: Array<MovieRating>): RecyclerView.Adapter<MyMovieRatingsAdapter.MyRatingViewHolder>() { class MyRatingViewHolder(val ratingView: ImageView) : RecyclerView.ViewHolder(ratingView) override fun onBindViewHolder(holder: MyRatingViewHolder, position: Int) { val ratingData = myData[position] holder.ratingView.contentDescription = "Movie ${position}: " + "${ratingData.title}, ${ratingData.starRating} stars" } }
Java
public class MovieRating { private String title; private int starRating; // ... public String getTitle() { return title; } public int getStarRating() { return starRating; } } public class MyMovieRatingsAdapter extends RecyclerView.Adapter<MyAdapter.MyRatingViewHolder> { private MovieRating[] myData; public static class MyRatingViewHolder extends RecyclerView.ViewHolder { public ImageView ratingView; public MyRatingViewHolder(ImageView iv) { super(iv); ratingView = iv; } } @Override public void onBindViewHolder(MyRatingViewHolder holder, int position) { MovieRating ratingData = myData[position]; holder.ratingView.setContentDescription("Movie " + position + ": " + ratingData.getTitle() + ", " + ratingData.getStarRating() + " stars") } }
İlgili içerik grupları
Uygulamanızda doğal bir grup oluşturan birkaç kullanıcı arayüzü öğesi (ör. bir şarkının ayrıntıları veya bir mesajın özellikleri) gösteriliyorsa bu öğeleri genellikle ViewGroup sınıfının bir alt sınıfı olan bir kapsayıcı içinde düzenleyin. Kapsayıcı nesnenin android:screenReaderFocusable özelliğini true, her bir iç nesnenin android:focusable özelliğini ise false olarak ayarlayın. Bu sayede, erişilebilirlik hizmetleri, iç öğelerin içerik açıklamalarını tek bir duyuruda sırayla sunabilir.
İlgili öğelerin bu şekilde birleştirilmesi, yardımcı teknoloji kullanıcılarının ekrandaki bilgileri daha verimli bir şekilde keşfetmesine yardımcı olur.
Aşağıdaki snippet, birbiriyle ilişkili içerik parçaları içerdiğinden kapsayıcı öğe olan ConstraintLayout örneğinin android:screenReaderFocusable özelliği true olarak, içteki TextView öğelerinin android:focusable özelliği ise false olarak ayarlanmıştır:
<!-- In response to a single user interaction, accessibility services announce both the title and the artist of the song. --> <ConstraintLayout android:id="@+id/song_data_container" ... android:screenReaderFocusable="true"> <TextView android:id="@+id/song_title" ... android:focusable="false" android:text="@string/my_song_title" /> <TextView android:id="@+id/song_artist" android:focusable="false" android:text="@string/my_songwriter" /> </ConstraintLayout>
Erişilebilirlik hizmetleri, iç öğelerin açıklamalarını tek bir ifadeyle duyurduğundan, öğenin anlamını aktarmaya devam ederken her açıklamayı mümkün olduğunca kısa tutmak önemlidir.
Not: Genel olarak, bir grubun alt öğelerinin metnini toplayarak grup için içerik açıklaması oluşturmaktan kaçınmanız gerekir. Bu durumda grubun açıklaması hassas hale gelir ve bir alt öğenin metni değiştiğinde grubun açıklaması artık görünür metinle eşleşmeyebilir.
Liste veya ızgara bağlamında, ekran okuyucu bir liste ya da ızgara öğesinin alt metin düğümlerinin metnini birleştirebilir. Bu duyuruyu değiştirmemeniz önerilir.
İç içe gruplar
Uygulamanızın arayüzünde çok boyutlu bilgiler (ör. festival etkinliklerinin günlük listesi) gösteriliyorsa iç grup kapsayıcılarında android:screenReaderFocusable özelliğini kullanın. Bu etiketleme şeması, ekranın içeriğini keşfetmek için gereken duyuru sayısı ile her duyurunun uzunluğu arasında iyi bir denge sağlar.
Aşağıdaki kod snippet'inde, daha büyük grupların içindeki grupları etiketlemenin bir yöntemi gösterilmektedir:
<!-- In response to a single user interaction, accessibility services announce the events for a single stage only. --> <ConstraintLayout android:id="@+id/festival_event_table" ... > <ConstraintLayout android:id="@+id/stage_a_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage A. --> </ConstraintLayout> <ConstraintLayout android:id="@+id/stage_b_event_column" android:screenReaderFocusable="true"> <!-- UI elements that describe the events on Stage B. --> </ConstraintLayout> </ConstraintLayout>
Metin içindeki başlıklar
Bazı uygulamalar, ekranda görünen metin gruplarını özetlemek için başlıkları kullanır. Belirli bir View öğesi başlığı temsil ediyorsa öğenin android:accessibilityHeading özelliğini true olarak ayarlayarak erişilebilirlik hizmetleri için amacını belirtebilirsiniz.
Erişilebilirlik hizmeti kullanıcıları, paragraflar veya kelimeler arasında gezinmek yerine başlıklar arasında gezinmeyi tercih edebilir. Bu esneklik, metinle gezinme deneyimini iyileştirir.
Erişilebilirlik bölmesi başlıkları
Android 9 (API düzeyi 28) ve sonraki sürümlerde, ekranın bölmeleri için erişilebilirlik dostu başlıklar sağlayabilirsiniz. Erişilebilirlik amacıyla kullanılan bölme, bir pencerenin görsel olarak farklı bir kısmıdır (ör. bir parçanın içeriği). Erişilebilirlik hizmetlerinin bir bölmenin pencere benzeri davranışını anlaması için uygulamanızın bölmelerine açıklayıcı başlıklar verin. Erişilebilirlik hizmetleri, bir bölmenin görünümü veya içeriği değiştiğinde kullanıcılara daha ayrıntılı bilgi sağlayabilir.
Bir bölmenin başlığını belirtmek için aşağıdaki snippet'te gösterildiği gibi android:accessibilityPaneTitle özelliğini kullanın:
<!-- Accessibility services receive announcements about content changes that are scoped to either the "shopping cart view" section (top) or "browse items" section (bottom) --> <MyShoppingCartView android:id="@+id/shoppingCartContainer" android:accessibilityPaneTitle="@string/shoppingCart" ... /> <MyShoppingBrowseView android:id="@+id/browseItemsContainer" android:accessibilityPaneTitle="@string/browseProducts" ... />
Dekoratif öğeler
Kullanıcı arayüzünüzdeki bir öğe yalnızca görsel aralık veya görsel görünüm amacıyla kullanılıyorsa android:importantForAccessibility özelliğini "no" olarak ayarlayın.
Erişilebilirlik işlemleri ekleme
Erişilebilirlik hizmeti kullanıcılarının, uygulamanızdaki tüm kullanıcı işlemleri akışlarını kolayca gerçekleştirmesine izin vermeniz önemlidir. Örneğin, bir kullanıcı listedeki bir öğeyi kaydırabiliyorsa bu işlem, erişilebilirlik hizmetlerine de sunulabilir. Böylece kullanıcılar aynı kullanıcı işlemleri akışını tamamlamak için alternatif bir yol kullanabilir.
Tüm işlemleri erişilebilir hale getirme
TalkBack, Sesli Erişim veya Anahtar Erişimi kullanıcısının, uygulamadaki belirli kullanıcı akışlarını tamamlamak için alternatif yöntemlere ihtiyacı olabilir. Sürükle ve bırakma veya kaydırma gibi hareketlerle ilişkili işlemler için uygulamanız, işlemleri erişilebilirlik hizmeti kullanıcılarının erişebileceği şekilde sunabilir.
Uygulama, erişilebilirlik işlemleri sayesinde kullanıcılara bir işlemi tamamlamanın alternatif yollarını sunabilir.
Örneğin, uygulamanızda kullanıcıların bir öğeyi kaydırmasına izin veriliyorsa bu işlevi şu şekilde özel bir erişilebilirlik işlemiyle de kullanıma sunabilirsiniz:
Kotlin
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive) ) { _, _ -> // Same method executed when swiping on itemView archiveItem() true }
Java
ViewCompat.addAccessibilityAction( // View to add accessibility action itemView, // Label surfaced to user by an accessibility service getText(R.id.archive), (view, arguments) -> { // Same method executed when swiping on itemView archiveItem(); return true; } );
With the custom accessibility action implemented, users can access the action through the actions menu.
Make available actions understandable
When a view supports actions such as touch & hold, an accessibility service such as TalkBack announces it as "Double tap and hold to long press."
This generic announcement doesn't give the user any context about what a touch & hold action does.
To make this announcement more descriptive, you can replace the accessibility action’s announcement like so:
Kotlin
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null )
Java
ViewCompat.replaceAccessibilityAction( // View that contains touch & hold action itemView, AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_LONG_CLICK, // Announcement read by TalkBack to surface this action getText(R.string.favorite), null );
This results in TalkBack announcing "Double tap and hold to favorite," helping users understand the purpose of the action.
Extend system widgets
Note: When you design your app's UI, use or extend
system-provided widgets that are as far down Android's class hierarchy as
possible. System-provided widgets that are far down the hierarchy already
have most of the accessibility capabilities your app needs. It's easier
to extend these system-provided widgets than to create your own from the more
generic View,
ViewCompat,
Canvas, and
CanvasCompat
classes.
If you must extend View or Canvas directly, which
might be necessary for a highly customized experience or a game level, see
Make custom views more
accessible.
This section uses the example of implementing a special type of
Switch called TriSwitch while following
best practices around extending system widgets. A TriSwitch
object works similarly to a Switch object, except that each instance of
TriSwitch allows the user to toggle among three possible states.
Extend from far down the class hierarchy
The Switch object inherits from several framework UI classes in its hierarchy:
View ↳ TextView ↳ Button ↳ CompoundButton ↳ Switch
Yeni TriSwitch sınıfının doğrudan Switch sınıfından genişletilmesi en iyisidir. Bu sayede, Android erişilebilirlik çerçevesi, TriSwitch sınıfının ihtiyaç duyduğu erişilebilirlik özelliklerinin çoğunu sağlar:
- Erişilebilirlik işlemleri: Erişilebilirlik hizmetlerinin,
TriSwitchnesnesinde gerçekleştirilen her olası kullanıcı girişini nasıl taklit edebileceğiyle ilgili sistem bilgileri. (Viewkaynağından devralındı.) - Erişilebilirlik etkinlikleri: Erişilebilirlik hizmetleri için, ekran yenilendiğinde veya güncellendiğinde
TriSwitchnesnesinin görünümünün değişebileceği her olası yöntemle ilgili bilgiler. (Viewkaynağından devralındı.) - Özellikler: Her
TriSwitchnesnesiyle ilgili ayrıntılar (ör. gösterdiği metinlerin içeriği). (TextViewkaynağından devralındı.) - Durum bilgileri:
TriSwitchnesnesinin mevcut durumunun açıklaması (ör. "işaretli" veya "işaretsiz"). (CompoundButtonkaynağından devralındı.) - Durumun metin açıklaması: Her durumun neyi temsil ettiğine dair metin tabanlı açıklama. (
Switchkaynağından devralındı.)
Switch ve üst sınıflarının bu davranışı, TriSwitch nesneleri için neredeyse aynıdır. Bu nedenle, uygulamanız olası durum sayısını ikiden üçe çıkarmaya odaklanabilir.
Özel etkinlikleri tanımlama
Bir sistem widget'ını genişlettiğinizde, kullanıcıların bu widget'la etkileşim kurma şeklinin bir yönünü değiştirmiş olursunuz. Erişilebilirlik hizmetlerinin, kullanıcının doğrudan widget'la etkileşimde bulunuyormuş gibi uygulamanızın widget'ını güncelleyebilmesi için bu etkileşim değişikliklerini tanımlamanız önemlidir.
Genel bir yönerge olarak, geçersiz kıldığınız her görünüm tabanlı geri çağırma için ViewCompat.replaceAccessibilityAction() öğesini geçersiz kılarak ilgili erişilebilirlik işlemini de yeniden tanımlamanız gerekir.
Uygulamanızın testlerinde, ViewCompat.performAccessibilityAction() işlevini çağırarak yeniden tanımlanan bu işlemlerin davranışını doğrulayabilirsiniz.
Bu ilke, TriSwitch nesneleri için nasıl çalışabilir?
Normal bir Switch nesnesinin aksine, bir TriSwitch nesnesine dokunulduğunda üç olası durum arasında geçiş yapılır. Bu nedenle, ilgili ACTION_CLICK erişilebilirlik işleminin güncellenmesi gerekir:
Kotlin
class TriSwitch(context: Context) : Switch(context) { // 0, 1, or 2 var currentState: Int = 0 private set init { updateAccessibilityActions() } private fun updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label) { view, args -> moveToNextState() }) } private fun moveToNextState() { currentState = (currentState + 1) % 3 } }
Java
public class TriSwitch extends Switch { // 0, 1, or 2 private int currentState; public int getCurrentState() { return currentState; } public TriSwitch() { updateAccessibilityActions(); } private void updateAccessibilityActions() { ViewCompat.replaceAccessibilityAction(this, ACTION_CLICK, action-label, (view, args) -> moveToNextState()); } private void moveToNextState() { currentState = (currentState + 1) % 3; } }
Ek kaynaklar
Uygulamanızı daha erişilebilir hale getirme hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara göz atın: