Oprócz podstawowych informacji, które zawiera element kompozycyjny, takich jak ciąg tekstowy elementu kompozycyjnego Text, przydatne mogą być dodatkowe informacje o elementach interfejsu.
Informacje o znaczeniu i roli komponentu w Compose są nazywane semantyką. Jest to sposób na dostarczanie dodatkowego kontekstu dotyczącego elementów kompozycyjnych usługom takim jak ułatwienia dostępu, autouzupełnianie i testowanie. Na przykład ikona aparatu może być wizualnie tylko obrazem, ale jej znaczenie semantyczne może brzmieć „Zrób zdjęcie”.
Łącząc odpowiednią semantykę z odpowiednimi interfejsami API Compose, przekazujesz usługom ułatwień dostępu jak najwięcej informacji o komponencie, które następnie decydują, jak go przedstawić użytkownikowi.
Interfejsy API Material i Compose UI oraz Foundation mają wbudowaną semantykę, która odpowiada ich konkretnej roli i funkcji, ale możesz też zmodyfikować tę semantykę w przypadku istniejących interfejsów API lub ustawić nową dla komponentów niestandardowych zgodnie z Twoimi wymaganiami.
Właściwości semantyczne
Właściwości semantyczne przekazują znaczenie odpowiedniego elementu kompozycyjnego. Na przykład funkcja kompozycyjna Text zawiera właściwość semantyczną text, ponieważ takie jest znaczenie tej funkcji. Icon zawiera właściwość contentDescription (jeśli została ustawiona przez dewelopera), która przekazuje w formie tekstowej znaczenie ikony.
Zastanów się, jak właściwości semantyczne przekazują znaczenie elementu kompozycyjnego. Rozważ Switch. Użytkownik zobaczy to w ten sposób:
Switch w stanie „Włączony” i „Wyłączony”.Aby opisać znaczenie tego elementu, możesz powiedzieć: „To jest przełącznik, czyli element, który można przełączać, w stanie »Włączony«. Możesz go kliknąć, aby wejść z nim w interakcję”.
Do tego właśnie służą właściwości semantyczne. Węzeł semantyki tego elementu Switch zawiera te właściwości, które można zobaczyć w narzędziu Layout Inspector:
Switch.Role wskazuje typ elementu. StateDescription opisuje, jak należy odwoływać się do stanu „Włączone”. Domyślnie jest to zlokalizowana wersja słowa „Włączone”, ale w zależności od kontekstu można ją doprecyzować (np. „Włączone”). ToggleableState to bieżący stan przełącznika. Właściwość OnClick odnosi się do metody używanej do interakcji z tym elementem.
Śledzenie właściwości semantycznych każdego elementu kompozycyjnego w aplikacji daje wiele możliwości:
- Usługi ułatwień dostępu używają właściwości do reprezentowania interfejsu wyświetlanego na ekranie i umożliwiają użytkownikom interakcję z nim. W przypadku elementu kompozycyjnego Switch TalkBack może ogłosić: „Włączone; przełącznik; kliknij dwukrotnie, aby przełączyć”. Użytkownik może dwukrotnie kliknąć ekran, aby wyłączyć przełącznik.
-
Platforma testowa używa właściwości do znajdowania węzłów, interakcji z nimi i tworzenia asercji:
val mySwitch = SemanticsMatcher.expectValue( SemanticsProperties.Role, Role.Switch ) composeTestRule.onNode(mySwitch) .performClick() .assertIsOff()
Komponenty i modyfikatory utworzone na podstawie biblioteki podstawowej Compose mają już domyślnie ustawione odpowiednie właściwości. Opcjonalnie możesz zmienić te właściwości ręcznie, aby poprawić obsługę ułatwień dostępu w konkretnych przypadkach użycia, lub zmienić strategię scalania lub czyszczenia funkcji kompozycyjnych.
Aby zasygnalizować usługom ułatwień dostępu konkretny typ treści komponentu, możesz zastosować różne semantyki. Te dodatki będą obsługiwać główne informacje semantyczne i pomogą usługom ułatwień dostępu dostosować sposób reprezentowania komponentu, ogłaszania go lub interakcji z nim.
Pełną listę właściwości semantycznych znajdziesz w opisie obiektu SemanticsProperties. Pełną listę możliwych działań związanych z ułatwieniami dostępu znajdziesz w obiekcie SemanticsActions.
Nagłówki
Aplikacje często zawierają ekrany ze szczegółowymi treściami tekstowymi, takimi jak długie artykuły lub strony z wiadomościami, które są zwykle podzielone na różne podsekcje z nagłówkami:
Użytkownicy z potrzebami związanymi z ułatwieniami dostępu mogą mieć trudności z poruszaniem się po takim ekranie. Aby ułatwić nawigację, niektóre usługi ułatwień dostępu umożliwiają łatwiejsze poruszanie się bezpośrednio między sekcjami lub nagłówkami. Aby to umożliwić, wskaż, że komponent jest elementem heading, definiując jego właściwość semantyczną:
@Composable private fun Subsection(text: String) { Text( text = text, style = MaterialTheme.typography.headlineSmall, modifier = Modifier.semantics { heading() } ) }
Alerty i wyskakujące okienka
Jeśli komponent jest alertem lub wyskakującym okienkiem, takim jak Snackbar, możesz zasygnalizować usługom ułatwień dostępu, że nowa struktura lub aktualizacje treści mogą być przekazywane użytkownikom.
Komponenty podobne do alertów można oznaczyć za pomocą właściwości semantycznej liveRegion. Dzięki temu usługi ułatwień dostępu mogą automatycznie powiadamiać użytkownika o zmianach w tym komponencie lub jego elementach podrzędnych:
PopupAlert( message = "You have a new message", modifier = Modifier.semantics { liveRegion = LiveRegionMode.Polite } )
W większości przypadków, gdy chcesz na krótko zwrócić uwagę użytkowników na alerty lub ważne zmieniające się treści na ekranie, używaj liveRegionMode.Polite.
Aby uniknąć zakłóceń, używaj liveRegion.Assertive oszczędnie.
Należy go używać w sytuacjach, gdy ważne jest, aby użytkownicy zostali poinformowani o treściach, które są istotne w danym momencie:
PopupAlert( message = "Emergency alert incoming", modifier = Modifier.semantics { liveRegion = LiveRegionMode.Assertive } )
Aktywnych regionów nie należy używać w przypadku treści, które są często aktualizowane, takich jak liczniki czasu, aby nie przytłaczać użytkowników ciągłymi powiadomieniami.
Komponenty przypominające okna
Komponenty niestandardowe przypominające okna, podobne do ModalBottomSheet, wymagają dodatkowych sygnałów, aby odróżnić je od otaczających treści. W tym celu możesz użyć semantyki paneTitle, aby wszelkie istotne zmiany w oknie lub panelu były odpowiednio reprezentowane przez usługi ułatwień dostępu wraz z głównymi informacjami semantycznymi:
ShareSheet( message = "Choose how to share this photo", modifier = Modifier .fillMaxWidth() .align(Alignment.TopCenter) .semantics { paneTitle = "New bottom sheet" } )
Aby dowiedzieć się więcej, zobacz, jak Material 3 używa paneTitle w swoich komponentach.
Komponenty błędu
W przypadku innych typów treści, takich jak komponenty podobne do błędów, możesz rozszerzyć główne informacje semantyczne dla użytkowników z potrzebami związanymi z ułatwieniami dostępu.
Definiując stany błędu, możesz poinformować usługi ułatwień dostępu o semantyce error i wyświetlać rozszerzone komunikaty o błędach.
W tym przykładzie TalkBack odczytuje główny tekst błędu, a następnie dodatkowe, rozwinięte komunikaty:
Error( errorText = "Fields cannot be empty", modifier = Modifier .semantics { error("Please add both email and password") } )
Komponenty śledzenia postępów
W przypadku komponentów niestandardowych, które śledzą postępy, możesz powiadamiać użytkowników o zmianach postępów, w tym o bieżącej wartości postępu, jego zakresie i rozmiarze kroku. Możesz to zrobić za pomocą semantyki progressBarRangeInfo – dzięki temu usługi ułatwień dostępu będą wiedzieć o zmianach postępu i będą mogły odpowiednio informować użytkowników. Różne technologie wspomagające osoby z niepełnosprawnością mogą też mieć unikalne sposoby sygnalizowania postępu.
ProgressInfoBar( modifier = Modifier .semantics { progressBarRangeInfo = ProgressBarRangeInfo( current = progress, range = 0F..1F ) } )
Informacje o liście i elementach
W przypadku niestandardowych list i siatek z wieloma elementami przydatne może być, aby usługi ułatwień dostępu otrzymywały też bardziej szczegółowe informacje, takie jak łączna liczba elementów i indeksów.
Dzięki zastosowaniu semantyki collectionInfo i collectionItemInfo odpowiednio do listy i elementów na tej długiej liście usługi ułatwień dostępu mogą informować użytkowników o tym, na którym indeksie elementu się znajdują, oprócz tekstowych informacji semantycznych:
MilkyWayList( modifier = Modifier .semantics { collectionInfo = CollectionInfo( rowCount = milkyWay.count(), columnCount = 1 ) } ) { milkyWay.forEachIndexed { index, text -> Text( text = text, modifier = Modifier.semantics { collectionItemInfo = CollectionItemInfo(index, 0, 0, 0) } ) } }
Opis stanu
Funkcja kompozycyjna może definiować stateDescription dla semantyki, której platforma Androida używa do odczytywania stanu, w którym znajduje się funkcja kompozycyjna. Na przykład przełączany element kompozycyjny może być w stanie „zaznaczony” lub „niezaznaczony”. W niektórych przypadkach możesz zastąpić domyślne etykiety opisu stanu używane przez Compose. Możesz to zrobić, wyraźnie określając etykiety opisu stanu przed zdefiniowaniem funkcji kompozycyjnej jako przełączanej:
@Composable private fun TopicItem(itemTitle: String, selected: Boolean, onToggle: () -> Unit) { val stateSubscribed = stringResource(R.string.subscribed) val stateNotSubscribed = stringResource(R.string.not_subscribed) Row( modifier = Modifier .semantics { // Set any explicit semantic properties stateDescription = if (selected) stateSubscribed else stateNotSubscribed } .toggleable( value = selected, onValueChange = { onToggle() } ) ) { /* ... */ } }
Działania niestandardowe
Działania niestandardowe mogą być używane w przypadku bardziej złożonych gestów na ekranie dotykowym, takich jak przesunięcie palcem, aby zamknąć, lub przeciągnij i upuść, ponieważ mogą one być trudne dla użytkowników z niepełnosprawnością ruchową lub innymi niepełnosprawnościami.
Aby ułatwić korzystanie z gestu przesunięcia w celu zamknięcia, możesz połączyć go z działaniem niestandardowym, przekazując działanie i etykietę zamknięcia:
SwipeToDismissBox( modifier = Modifier.semantics { // Represents the swipe to dismiss for accessibility customActions = listOf( CustomAccessibilityAction( label = "Remove article from list", action = { removeArticle() true } ) ) }, state = rememberSwipeToDismissBoxState(), backgroundContent = {} ) { ArticleListItem() }
Usługa ułatwień dostępu, taka jak TalkBack, podświetla komponent i podpowiada, że w jego menu dostępnych jest więcej działań, w tym działanie przesunięcia w celu odrzucenia:
Innym przypadkiem użycia działań niestandardowych są długie listy z elementami, które mają więcej dostępnych działań, ponieważ dla użytkowników może być uciążliwe przechodzenie przez każde działanie dla każdego elementu z osobna:
Aby ułatwić nawigację, co jest szczególnie przydatne w przypadku technologii wspomagających osoby z niepełnosprawnością opartych na interakcji, takich jak Switch Access czy Voice Access, możesz użyć działań niestandardowych w kontenerze, aby przenieść działania z poszczególnych przejść do osobnego menu czynności:
ArticleListItemRow( modifier = Modifier .semantics { customActions = listOf( CustomAccessibilityAction( label = "Open article", action = { openArticle() true } ), CustomAccessibilityAction( label = "Add to bookmarks", action = { addToBookmarks() true } ), ) } ) { Article( modifier = Modifier.clearAndSetSemantics { }, onClick = openArticle, ) BookmarkButton( modifier = Modifier.clearAndSetSemantics { }, onClick = addToBookmarks, ) }
W takich przypadkach pamiętaj, aby ręcznie wyczyścić oryginalną semantykę elementów podrzędnych za pomocą modyfikatora clearAndSetSemantics, ponieważ przenosisz je do działań niestandardowych.
Na przykładzie funkcji Switch Access: jej menu otwiera się po wybraniu kontenera i zawiera listę dostępnych działań zagnieżdżonych:
Drzewo semantyki
Kompozycja opisuje interfejs aplikacji i jest tworzona przez uruchamianie funkcji kompozycyjnych. Kompozycja to struktura drzewa, która składa się z funkcji kompozycyjnych opisujących interfejs.
Obok funkcji kompozycyjnej istnieje równoległe drzewo, które nazywa się drzewem semantycznym. To drzewo opisuje interfejs użytkownika w alternatywny sposób, który jest zrozumiały dla usług ułatwień dostępu i platformy testowej. Usługi ułatwień dostępu używają drzewa, aby opisać aplikację użytkownikom o określonych potrzebach. Platforma testowa używa drzewa do interakcji z aplikacją i tworzenia asercji na jej temat. Drzewo semantyczne nie zawiera informacji o tym, jak rysować elementy kompozycyjne, ale zawiera informacje o ich znaczeniu semantycznym.
Jeśli aplikacja składa się z komponentów i modyfikatorów z biblioteki Compose Foundation i Material, drzewo semantyczne jest automatycznie wypełniane i generowane. Jednak gdy dodajesz niestandardowe komponenty niskiego poziomu, musisz ręcznie podać ich semantykę. Może się też zdarzyć, że drzewo nie będzie prawidłowo lub w pełni odzwierciedlać znaczenia elementów na ekranie. W takim przypadku możesz je dostosować.
Weźmy na przykład ten niestandardowy komponent kalendarza:
W tym przykładzie cały kalendarz jest zaimplementowany jako pojedyncza funkcja kompozycyjna niskiego poziomu, która korzysta z funkcji kompozycyjnej Layout i rysuje bezpośrednio w Canvas.
Jeśli nie zrobisz nic więcej, usługi ułatwień dostępu nie otrzymają wystarczającej ilości informacji o zawartości funkcji kompozycyjnej i wyborze użytkownika w kalendarzu. Jeśli na przykład użytkownik kliknie dzień zawierający liczbę 17, platforma ułatwień dostępu otrzyma tylko informacje o opisie całego elementu sterującego kalendarza. W tym przypadku usługa ułatwień dostępu TalkBack odczyta „Kalendarz” lub, co jest tylko trochę lepsze, „Kalendarz na kwiecień”, a użytkownik będzie musiał się domyślać, który dzień został wybrany. Aby ten element kompozycyjny był bardziej dostępny, musisz ręcznie dodać informacje semantyczne.
Scalone i niescalone drzewo
Jak wspomnieliśmy wcześniej, każdy element kompozycyjny w drzewie interfejsu może mieć ustawione zero lub więcej właściwości semantycznych. Jeśli element kompozycyjny nie ma ustawionych właściwości semantycznych, nie jest uwzględniany w drzewie semantycznym. Dzięki temu drzewo semantyki zawiera tylko węzły, które faktycznie mają znaczenie semantyczne. Czasami jednak, aby przekazać prawidłowe znaczenie tego, co jest wyświetlane na ekranie, warto też scalić niektóre poddrzewa węzłów i traktować je jako jeden węzeł. Dzięki temu możesz traktować zbiór węzłów jako całość, zamiast zajmować się każdym węzłem podrzędnym z osobna. Zasadniczo każdy węzeł w tym drzewie reprezentuje element, który można zaznaczyć podczas korzystania z usług ułatwień dostępu.
Przykładem takiego elementu kompozycyjnego jest Button. Przycisk możesz traktować jako pojedynczy element, nawet jeśli zawiera wiele węzłów podrzędnych:
Button(onClick = { /*TODO*/ }) { Icon( imageVector = Icons.Filled.Favorite, contentDescription = null ) Spacer(Modifier.size(ButtonDefaults.IconSpacing)) Text("Like") }
W drzewie semantyki właściwości elementów podrzędnych przycisku są scalane, a przycisk jest przedstawiany jako pojedynczy węzeł liścia w drzewie:
Komponenty i modyfikatory mogą wskazywać, że chcą scalić właściwości semantyczne elementów podrzędnych, wywołując Modifier.semantics
(mergeDescendants = true) {}. Ustawienie tej właściwości na true oznacza, że właściwości semantyki powinny zostać scalone. W przykładzie Button funkcja kompozycyjna Button używa wewnętrznie modyfikatora clickable, który zawiera ten modyfikator semantics. Dlatego węzły podrzędne przycisku są scalane.
Więcej informacji o tym, kiedy należy zmienić zachowanie scalania w funkcji kompozycyjnej, znajdziesz w dokumentacji dotyczącej ułatwień dostępu.
Ta właściwość jest ustawiona w przypadku kilku modyfikatorów i funkcji kompozycyjnych w bibliotekach Foundation i Material Compose. Na przykład modyfikatory clickable i toggleable automatycznie scalą swoje elementy podrzędne. Dodatkowo funkcja kompozycyjna ListItem scali swoje elementy podrzędne.
Sprawdzanie drzewa
Drzewo semantyczne to w rzeczywistości 2 różne drzewa. Istnieje scalone drzewo semantyczne, które scala węzły podrzędne, gdy właściwość mergeDescendants jest ustawiona na true.
Istnieje też niescalone drzewo semantyki, które nie stosuje scalania, ale zachowuje każdy węzeł w stanie nienaruszonym. Usługi ułatwień dostępu używają niescalonego drzewa i stosują własne algorytmy scalania, biorąc pod uwagę właściwość mergeDescendants. Platforma testowa domyślnie używa scalonego drzewa.
Oba drzewa możesz sprawdzić za pomocą metody printToLog(). Domyślnie, tak jak we wcześniejszych przykładach, scalone drzewo jest rejestrowane. Aby zamiast tego wydrukować niescalone drzewo, ustaw parametr useUnmergedTree dopasowania onRoot() na true:
composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")
Narzędzie Layout Inspector umożliwia wyświetlanie zarówno scalonego, jak i niescalonego drzewa semantyki. Aby wybrać preferowane drzewo, użyj filtra widoku:
W przypadku każdego węzła w drzewie narzędzie Layout Inspector pokazuje w panelu właściwości zarówno semantykę scaloną, jak i semantykę ustawioną w tym węźle:
Domyślnie dopasowania w platformie testowej używają scalonego drzewa semantycznego.
Dlatego możesz wejść w interakcję z elementem Button, dopasowując tekst wyświetlany wewnątrz niego:
composeTestRule.onNodeWithText("Like").performClick()
Możesz zastąpić to zachowanie, ustawiając parametr useUnmergedTree dopasowań na true, tak jak w przypadku dopasowania onRoot.
Dostosowywanie drzewa
Jak wspomnieliśmy wcześniej, możesz zastąpić lub wyczyścić niektóre właściwości semantyczne albo zmienić zachowanie scalania drzewa. Jest to szczególnie ważne, gdy tworzysz własne komponenty niestandardowe. Jeśli nie ustawisz prawidłowych właściwości i zachowania scalania, aplikacja może być niedostępna, a testy mogą działać inaczej, niż oczekujesz. Jeśli chcesz dowiedzieć się więcej o testowaniu, zapoznaj się z przewodnikiem po testowaniu.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Ułatwienia dostępu w Compose
- Material Design 2 w Compose
- Testowanie układu Compose