Pojęcia i implementacja w Jetpack Compose
Aby ułatwić korzystanie z urządzeń użytkownikom z niepełnosprawnościami, platforma Android umożliwia tworzenie usług ułatwień dostępu, które mogą prezentować treści z aplikacji i obsługiwać je w imieniu użytkowników.
Android udostępnia kilka systemowych usług ułatwień dostępu, w tym:
- TalkBack: pomaga osobom niedowidzącym i niewidomym. Odczytuje treści za pomocą syntezatora mowy i wykonuje działania w aplikacji w odpowiedzi na gesty użytkownika.
- Switch Access: pomaga osobom z niepełnosprawnością ruchową. Podświetla elementy interaktywne i wykonuje działania w odpowiedzi na naciśnięcie przycisku przez użytkownika. Umożliwia sterowanie urządzeniem za pomocą tylko 1 lub 2 przycisków.
Aby osoby z niepełnosprawnościami mogły bez problemu korzystać z Twojej aplikacji, musi ona być zgodna ze sprawdzonymi metodami opisanymi na tej stronie, które bazują na wytycznych opisanych w artykule Ułatwianie dostępu do aplikacji.
Elementy etykiety
Ważne jest, aby udostępniać użytkownikom przydatne i opisowe etykiety każdego interaktywnego elementu interfejsu w aplikacji. Każda etykieta musi wyjaśniać znaczenie i przeznaczenie danego elementu. Czytniki ekranu, takie jak TalkBack, mogą odczytywać te etykiety użytkownikom.
W większości przypadków opis elementu interfejsu podajesz w pliku zasobu układu, który zawiera ten element. Zwykle etykiety dodaje się za pomocą atrybutu contentDescription, jak wyjaśniono w przewodniku zwiększającym dostępność aplikacji. W kolejnych sekcjach opisujemy kilka innych technik etykietowania.
Elementy, które można edytować
Podczas oznaczania elementów możliwych do edytowania, takich jak obiekty EditText, warto wyświetlać tekst, który zawiera przykład prawidłowych danych wejściowych w samym elemencie, a także udostępniać ten tekst czytnikom ekranu. W takich sytuacjach możesz użyć atrybutu android:hint, jak pokazano w tym fragmencie kodu:
<!-- The hint text for en-US locale would be "Apartment, suite, or building". --> <EditText android:id="@+id/addressLine2" android:hint="@string/aptSuiteBuilding" ... />
W takiej sytuacji obiekt View musi mieć atrybut android:labelFor ustawiony na identyfikator elementu EditText. Więcej informacji znajdziesz w sekcji poniżej.
Pary elementów, w których jeden opisuje drugi
Często element EditText ma odpowiadający mu obiekt View, który opisuje, co użytkownicy muszą wpisać w elemencie EditText. Możesz wskazać tę relację, ustawiając atrybut android:labelFor obiektu View.
Przykład etykietowania takich par elementów znajdziesz w tym fragmencie kodu:
<!-- 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" ... />
Elementy w kolekcji
Podczas dodawania etykiet do elementów kolekcji każda etykieta musi być niepowtarzalna. Dzięki temu usługi ułatwień dostępu w systemie mogą odwoływać się do dokładnie jednego elementu na ekranie podczas ogłaszania etykiety. Dzięki temu użytkownicy wiedzą, kiedy przechodzą między elementami interfejsu lub kiedy przenoszą fokus na element, który już odkryli.
W szczególności dodaj dodatkowy tekst lub informacje kontekstowe do elementów w ponownie używanych układach, takich jak obiekty RecyclerView, aby każdy element podrzędny był jednoznacznie identyfikowany.
Aby to zrobić, ustaw opis treści w ramach implementacji adaptera, jak pokazano w tym fragmencie kodu:
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") } }
Grupy powiązanych treści
Jeśli aplikacja wyświetla kilka elementów interfejsu, które tworzą naturalną grupę, np. szczegóły utworu lub atrybuty wiadomości, umieść te elementy w kontenerze, który zwykle jest podklasą ViewGroup. Ustaw atrybut android:screenReaderFocusable obiektu kontenera na true, a atrybut android:focusable każdego obiektu wewnętrznego na false. Dzięki temu usługi ułatwień dostępu mogą prezentować opisy treści elementów wewnętrznych jeden po drugim w jednym komunikacie.
Konsolidacja powiązanych elementów pomaga użytkownikom technologii wspomagającej osoby z niepełnosprawnością łatwiej znajdować informacje na ekranie.
Poniższy fragment kodu zawiera części treści, które są ze sobą powiązane, więc element kontenera, czyli instancja ConstraintLayout, ma atrybut android:screenReaderFocusable ustawiony na true, a elementy wewnętrzne TextView mają atrybut android:focusable ustawiony na false:
<!-- 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>
Usługi ułatwień dostępu odczytują opisy elementów wewnętrznych w jednym komunikacie, dlatego ważne jest, aby każdy opis był jak najkrótszy, ale jednocześnie przekazywał znaczenie elementu.
Uwaga: na ogół nie należy tworzyć opisu treści dla grupy przez agregowanie tekstu jej elementów podrzędnych. W takim przypadku opis grupy staje się podatny na błędy, a gdy tekst elementu podrzędnego ulegnie zmianie, opis grupy może już nie pasować do widocznego tekstu.
W kontekście listy lub siatki czytnik ekranu może łączyć tekst węzłów tekstowych podrzędnych elementu listy lub siatki. Najlepiej nie modyfikować tego ogłoszenia.
Zagnieżdżone grupy
Jeśli interfejs aplikacji zawiera informacje wielowymiarowe, np. listę wydarzeń festiwalowych z podziałem na dni, użyj atrybutu android:screenReaderFocusable w wewnętrznych kontenerach grup. Ten schemat etykietowania zapewnia dobrą równowagę między liczbą komunikatów potrzebnych do odkrycia zawartości ekranu a długością każdego komunikatu.
Poniższy fragment kodu pokazuje jedną z metod oznaczania grup w większych grupach:
<!-- 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>
Nagłówki w tekście
Niektóre aplikacje używają nagłówków do podsumowywania grup tekstu wyświetlanych na ekranie. Jeśli konkretny element View reprezentuje nagłówek, możesz wskazać jego przeznaczenie dla usług ułatwień dostępu, ustawiając atrybut android:accessibilityHeading elementu na true.
Użytkownicy usług ułatwień dostępu mogą przechodzić między nagłówkami zamiast między akapitami lub słowami. Ta elastyczność poprawia nawigację tekstową.
Tytuły paneli ułatwień dostępu
W Androidzie 9 (API na poziomie 28) i nowszych wersjach możesz podać tytuły paneli ekranu, które są przyjazne dla osób z niepełnosprawnością. Ze względu na ułatwienia dostępu panel to wizualnie odrębna część okna, np. zawartość fragmentu. Aby usługi ułatwień dostępu mogły zrozumieć zachowanie panelu podobne do okna, nadaj panelom aplikacji opisowe tytuły. Usługi ułatwień dostępu mogą wtedy przekazywać użytkownikom bardziej szczegółowe informacje, gdy zmieni się wygląd lub zawartość panelu.
Aby określić tytuł panelu, użyj atrybutu android:accessibilityPaneTitle, jak pokazano w tym fragmencie kodu:
<!-- 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" ... />
Elementy dekoracyjne
Jeśli element interfejsu istnieje tylko w celu zapewnienia odstępu lub wyglądu, ustaw jego atrybut android:importantForAccessibility na "no".
Dodawanie działań związanych z ułatwieniami dostępu
Ważne jest, aby użytkownicy usług ułatwień dostępu mogli łatwo wykonywać wszystkie ścieżki użytkownika w aplikacji. Jeśli na przykład użytkownik może przesunąć element na liście, to działanie może być też udostępnione usługom ułatwień dostępu, aby użytkownicy mieli alternatywny sposób na wykonanie tej samej ścieżki.
Udostępnianie wszystkich działań
Użytkownik TalkBack, Voice Access lub Switch Access może potrzebować alternatywnych sposobów na wykonanie określonych ścieżek użytkownika w aplikacji. W przypadku działań związanych z gestami, takimi jak przeciąganie i upuszczanie lub przesuwanie, aplikacja może udostępniać działania w sposób dostępny dla użytkowników usług ułatwień dostępu.
Za pomocą działań związanych z ułatwieniami dostępu aplikacja może udostępniać użytkownikom alternatywne sposoby wykonania działania.
Jeśli na przykład aplikacja umożliwia użytkownikom przesuwanie elementów, możesz też udostępnić tę funkcję za pomocą niestandardowego działania ułatwień dostępu, np. w ten sposób:
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
Najlepiej, aby nowa klasa TriSwitch rozszerzała bezpośrednio klasę Switch. Dzięki temu platforma ułatwień dostępu na Androidzie zapewnia większość funkcji ułatwień dostępu, których potrzebuje klasa TriSwitch:
- Działania związane z ułatwieniami dostępu: informacje dla systemu o tym, jak usługi ułatwień dostępu mogą emulować każde możliwe dane wejściowe użytkownika wykonane na obiekcie
TriSwitch. (Odziedziczone z:View). - Wydarzenia związane z ułatwieniami dostępu: informacje dla usług ułatwień dostępu o każdym możliwym sposobie zmiany wyglądu obiektu
TriSwitchpodczas odświeżania lub aktualizowania ekranu. (Odziedziczone z:View). - Charakterystyka: szczegóły dotyczące każdego
TriSwitchobiektu, np. zawartość wyświetlanego tekstu. (Odziedziczone z:TextView). - Informacje o stanie: opis bieżącego stanu obiektu
TriSwitch, np. „zaznaczony” lub „niezaznaczony”. (Odziedziczone z:CompoundButton). - Opis tekstowy stanu: tekstowe wyjaśnienie, co oznacza każdy stan. (Odziedziczone z:
Switch).
To zachowanie klasy Switch i jej nadklas jest niemal identyczne z zachowaniem obiektów TriSwitch. Dlatego możesz skupić się na zwiększeniu liczby możliwych stanów z dwóch do trzech.
Definiowanie zdarzeń niestandardowych
Gdy rozszerzasz widżet systemowy, prawdopodobnie zmieniasz sposób, w jaki użytkownicy wchodzą z nim w interakcję. Ważne jest, aby zdefiniować te zmiany interakcji, aby usługi ułatwień dostępu mogły aktualizować widżet aplikacji tak, jakby użytkownik wchodził z nim w interakcję bezpośrednio.
Ogólna zasada jest taka, że w przypadku każdego wywołania zwrotnego opartego na widoku, które zastępujesz, musisz też ponownie zdefiniować odpowiednie działanie związane z ułatwieniami dostępu, zastępując metodę ViewCompat.replaceAccessibilityAction().
W testach aplikacji możesz sprawdzić działanie tych przedefiniowanych działań, wywołując funkcję ViewCompat.performAccessibilityAction().
Jak ta zasada może działać w przypadku obiektów TriSwitch
W przeciwieństwie do zwykłego obiektu Switch kliknięcie obiektu TriSwitch powoduje przełączanie między 3 możliwymi stanami. Dlatego należy zaktualizować odpowiednie działanie ACTION_CLICK ułatwień dostępu:
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; } }
Dodatkowe materiały
Więcej informacji o zwiększaniu dostępności aplikacji znajdziesz w tych materiałach: