Semantyka w funkcji tworzenia

Kompozycja opisuje interfejs aplikacji i jest tworzona przez uruchomienie funkcji kompozycyjnych. Kompozycja to struktura drzewa, która składa się z komponentów kompozycyjnych opisujących interfejs użytkownika.

Obok kompozycji znajduje się równoległe drzewo o nazwie drzewo semantyczne. To drzewo opisuje interfejs w alternatywny sposób, który jest zrozumiały dla usług ułatwień dostępu i platformy testowania. Usługi ułatwień dostępu używają drzewa do opisania aplikacji użytkownikom, którzy potrzebują tej aplikacji. Platforma testowa wykorzystuje drzewo do interakcji z aplikacją i wyciągania na nią asercji. Drzewo semantyczne nie zawiera informacji o rysowaniukomponentów, ale zawiera informacje o znaczeniuznakówelementów kompozycyjnych

Typowa hierarchia interfejsu i jego drzewo semantyczne
Rysunek 1. Typowa hierarchia interfejsu i jej drzewo semantyczne.

Jeśli aplikacja składa się z kompozycji i modyfikatorów z podstawowych funkcji tworzenia i biblioteki materiałów, drzewo semantyki zostanie automatycznie wypełnione i wygenerowane. Jeśli jednak dodajesz niestandardowe elementy kompozycyjne niskiego poziomu, musisz określić ich semantykę ręcznie. Mogą też wystąpić sytuacje, gdy drzewo nie oddaje poprawnie lub w pełni znaczenia elementów na ekranie. W takich przypadkach możesz dostosować drzewo.

Weźmy na przykład ten niestandardowy kalendarz kompozycyjny:

Niestandardowy kalendarz kompozycyjny z elementami dnia z możliwością wyboru
Rysunek 2. Niestandardowy kalendarz z możliwością wyboru z elementami dnia, które można wybrać.

W tym przykładzie cały kalendarz został wdrożony jako jeden niskopoziomowy element kompozycyjny przy użyciu funkcji kompozycyjnej Layout i rysunku bezpośrednio w elemencie Canvas. Jeśli nic więcej nie zrobisz, usługi ułatwień dostępu nie będą otrzymywać wystarczających informacji o zawartości elementu kompozycyjnego i wyborze użytkownika w kalendarzu. Jeśli na przykład użytkownik kliknie dzień zawierający wartość 17, platforma ułatwień dostępu otrzyma tylko opis dla całego ustawienia kalendarza. W takim przypadku usługa ułatwień dostępu TalkBack powiadamia „Kalendarz” lub, tylko trochę lepiej, „kalendarz kwietniowy”, a użytkownik mógł się zastanawiać, jaki dzień został wybrany. Aby ułatwić dostęp do tego elementu kompozycyjnego, musisz ręcznie dodać informacje semantyczne.

Właściwości semantyczne

Wszystkie węzły w drzewie interfejsu o określonym znaczeniu semantycznym mają w drzewie semantycznym węzeł równoległy. Węzeł w drzewie semantycznym zawiera te właściwości, które przekazują znaczenie odpowiedniego elementu kompozycyjnego. Na przykład element kompozycyjny Text zawiera właściwość semantyczną text, bo to jest znaczenie tego elementu kompozycyjnego. Element Icon zawiera właściwość contentDescription (jeśli została ustawiona przez programistę), która informuje w formie tekstowej, jakie jest znaczenie elementu Icon. Elementy kompozycyjne i modyfikatory, które są utworzone na podstawie biblioteki podstawowej tworzenia, ustawiają już odpowiednie właściwości. Opcjonalnie możesz ustawić lub zastąpić właściwości samodzielnie modyfikatorami semantics i clearAndSetSemantics. Na przykład dodaj do węzła niestandardowe działania związane z ułatwieniami dostępu, podaj alternatywny opis stanu elementu z możliwością przełączania lub wskaż, że określony tekst kompozycyjny powinien być traktowany jako nagłówek.

Aby zwizualizować drzewo semantyki, użyj narzędzia Inspektor układu lub metody printToLog() w testach. Spowoduje to wydrukowanie bieżącego drzewa semantyki w narzędziu Logcat.

class MyComposeTest {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun MyTest() {
        // Start the app
        composeTestRule.setContent {
            MyTheme {
                Text("Hello world!")
            }
        }
        // Log the full semantics tree
        composeTestRule.onRoot().printToLog("MY TAG")
    }
}

Wynik tego testu to:

    Printing with useUnmergedTree = 'false'
    Node #1 at (l=0.0, t=63.0, r=221.0, b=120.0)px
     |-Node #2 at (l=0.0, t=63.0, r=221.0, b=120.0)px
       Text = '[Hello world!]'
       Actions = [GetTextLayoutResult]

Zastanów się, w jaki sposób właściwości semantyczne przekazują znaczenie elementu kompozycyjnego. Rozważmy cel Switch. Użytkownik zobaczy to w taki sposób:

Rysunek 3. Przełącznik w stanie „Wł.” i „Wył.”.

Aby opisać znaczenie tego elementu, możesz powiedzieć: „To jest Switch. Jest to element, który można przełączać. Wystarczy kliknąć."

Do tego właśnie służą właściwości semantyczne. Węzeł semantyczny tego elementu Switch zawiera następujące właściwości, jak pokazano w inspektorze układu:

Inspektor układu pokazujący właściwości semantyki elementu kompozycyjnego Switch
Rysunek 4. Inspektor układu pokazujący właściwości semantyczne elementu kompozycyjnego Switch.

Pole Role wskazuje typ elementu. StateDescription określa, jak należy się odwoływać do stanu „Włączone”. Domyślnie jest to zlokalizowana wersja słowa „Włączone”, ale można ją sprecyzować (np. „Włączone”) w zależności od kontekstu. ToggleableState to bieżący stan przełącznika. Właściwość OnClick odwołuje się do metody używanej do interakcji z tym elementem. Pełną listę właściwości semantyki znajdziesz w obiekcie SemanticsProperties. Pełną listę możliwych działań związanych z ułatwieniami dostępu znajdziesz w obiekcie SemanticsActions.

Monitorowanie właściwości semantycznych każdego elementu kompozycyjnego w aplikacji otwiera mnóstwo zaawansowanych możliwości. Przykłady:

  • TalkBack korzysta z właściwości do odczytywania na głos tego, co jest wyświetlane na ekranie, i umożliwia użytkownikowi płynne korzystanie z nich. W przypadku funkcji przełączania kompozycyjnego TalkBack może powiedzieć: „Włączono; przełącz; kliknij dwukrotnie, aby przełączyć”. Użytkownik może kliknąć dwukrotnie ekran, aby wyłączyć przełącznik.
  • Platforma do testowania używa właściwości do znajdowania węzłów, interakcji z nimi i dokonywania asercji. Przykładowy test funkcji Switch może wyglądać tak:
    val mySwitch = SemanticsMatcher.expectValue(
        SemanticsProperties.Role, Role.Switch
    )
    composeTestRule.onNode(mySwitch)
        .performClick()
        .assertIsOff()

Scalone i niescalone drzewo semantyki

Jak już wspomnieliśmy, każdy element kompozycyjny w drzewie interfejsu może mieć 0 lub więcej właściwości semantytycznych. Gdy funkcja kompozycyjna nie ma ustawionych właściwości semantycznych, nie jest uwzględniany w drzewie semantyki. Dzięki temu drzewo semantyczne będzie zawierać tylko te węzły, które mają znaczenie semantyczne. Czasem jednak, aby przekazać poprawne znaczenie tego, co widać na ekranie, warto też scalić niektóre poddrzewa węzłów i potraktować je jako jedno. W ten sposób możesz wyciągać wnioski dotyczące zbioru węzłów jako całości, zamiast zajmować się każdym węzłem podrzędnym oddzielnie. Zgodnie z ogólną zasadą 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 tego elementu kompozycyjnego jest Button. Można myśleć o jednym przycisku jako 1 elementu, nawet jeśli może on 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 semantycznym właściwości elementów podrzędnych przycisku są scalone, a przycisk jest przedstawiony jako węzeł pojedynczego liścia:

Reprezentacja semantyki pojedynczego liścia
Rysunek 5. Scalona reprezentacja semantyki pojedynczego liścia.

Elementy kompozycyjne i modyfikatory mogą wskazywać, że chcą scalić właściwości semantyczne elementów podrzędnych, wywołując metodę Modifier.semantics (mergeDescendants = true) {}. Ustawienie tej właściwości na true wskazuje, że właściwości semantyczne powinny zostać scalone. W przykładzie Button funkcja kompozycyjna Button korzysta wewnętrznie z modyfikatora clickable, który zawiera ten modyfikator semantics. W ten sposób węzły podrzędne przycisku zostaną scalone. Przeczytaj dokumentację ułatwień dostępu, aby dowiedzieć się, kiedy należy zmienić sposób łączenia w komponencie.

Kilka modyfikatorów i funkcji kompozycyjnych w bibliotekach Podstawowe i Material Compose ma ustawioną tę właściwość. Na przykład modyfikatory clickable i toggleable automatycznie scalą swoje elementy podrzędne. Oprócz tego funkcja kompozycyjna ListItem scali swoje elementy podrzędne.

Skontroluj drzewa

Drzewo semantyczne to w rzeczywistości dwa różne drzewa. Jest scalone drzewo semantyczne, które scala węzły podrzędne, gdy parametr mergeDescendants ma wartość true. Jest też niescalone drzewo semantyczne, które nie stosuje scalania, ale zachowuje wszystkie węzły. Usługi ułatwień dostępu korzystają z niescalonego drzewa i stosują własne algorytmy scalania z uwzględnieniem właściwości mergeDescendants. Platforma testowa domyślnie używa scalonego drzewa.

Oba drzewa możesz zbadać za pomocą metody printToLog(). Domyślnie, tak jak we wcześniejszych przykładach, scalone drzewo jest logowane. Aby zamiast tego wydrukować niescalone drzewo, ustaw parametr useUnmergedTree w dopasowywaniu onRoot() na true:

composeTestRule.onRoot(useUnmergedTree = true).printToLog("MY TAG")

Inspektor układu pozwala wyświetlić zarówno scalone, jak i niescalone drzewo semantyczne, wybierając je w filtrze widoku:

Opcje widoku Inspektora układów umożliwiające wyświetlanie zarówno scalonego, jak i niescalonego drzewa semantycznego
Rysunek 6. Opcje widoku Inspektora układu, umożliwiające wyświetlanie zarówno scalonego, jak i niescalonego drzewa semantycznego.

W każdym węźle drzewa Inspektor układu wyświetla w panelu właściwości zarówno scaloną semantyki, jak i zestaw semantyki w tym węźle:

Właściwości semantyki zostały scalone i ustawione
Rysunek 7. Właściwości semantyki zostały scalone i ustawione.

Domyślnie dopasowania w platformie testowania używają scalonego drzewa semantycznego. Dlatego możesz wchodzić w interakcje z elementem Button, dopasowując wyświetlany w nim tekst:

composeTestRule.onNodeWithText("Like").performClick()

Aby zastąpić to działanie, ustaw parametr useUnmergedTree dopasowań na true, tak jak w przypadku dopasowania onRoot.

Zachowanie scalania

Jak przebiega scalanie, gdy funkcja kompozycyjna wskazuje, że powinny zostać scalone jej elementy potomne?

Każda właściwość semantyczna ma zdefiniowaną strategię scalania. Na przykład właściwość ContentDescription dodaje do listy wszystkie podrzędne wartości ContentDescription. Sprawdź strategię scalania właściwości semantycznej, sprawdzając jej implementację mergePolicy w narzędziu SemanticsProperties.kt. Właściwości mogą przyjmować wartości nadrzędne lub podrzędne, scalać je w listę lub ciąg znaków, nie zezwalać na scalanie i zamiast tego zgłaszać wyjątek ani jakąkolwiek inną niestandardową strategię scalania.

Pamiętaj, że komponenty podrzędne, które same ustawiły mergeDescendants = true, nie są uwzględniane w scalaniu. Przeanalizujmy przykład:

Element listy z obrazem, tekstem i ikoną zakładki
Rysunek 8. Pozycja listy z obrazem, tekstem i ikoną zakładki.

To jest element listy, który można kliknąć. Gdy użytkownik kliknie wiersz, aplikacja przejdzie na stronę z informacjami o artykule, na której może go przeczytać. W elemencie listy znajduje się przycisk dodawania artykułu do zakładek, który tworzy zagnieżdżony element możliwy do kliknięcia. W ten sposób przycisk będzie widoczny osobno w scalonym drzewie. Pozostała treść wiersza jest scalona:

Scalone drzewo zawiera wiele tekstów na liście w węźle wiersza. Niescalone drzewo zawiera oddzielne węzły dla każdego tekstu kompozycyjnego.
Rysunek 9. Scalone drzewo zawiera wiele tekstów na liście w węźle wiersza. Niescalone drzewo zawiera oddzielne węzły dla każdego tekstu kompozycyjnego.

Dostosowywanie drzewa semantyki

Jak już wspomnieliśmy, możesz zastąpić lub wyczyścić niektóre właściwości semantyczne bądź zmienić sposób scalania drzewa. Jest to szczególnie przydatne, gdy tworzysz własne komponenty niestandardowe. Bez ustawienia odpowiednich właściwości i sposobu scalania aplikacja może być niedostępna, a testy mogą działać inaczej niż oczekiwano. Aby dowiedzieć się więcej o niektórych typowych przypadkach użycia, w których warto dostosować drzewo semantyczne, zapoznaj się z dokumentacją ułatwień dostępu. Więcej informacji o testowaniu znajdziesz w przewodniku po testach.

Dodatkowe materiały