scalanie i czyszczenie,

Podczas poruszania się po elementach na ekranie usługi ułatwień dostępu muszą mieć możliwość ich grupowania, rozdzielania, a nawet ukrywania na odpowiednim poziomie szczegółowości. Jeśli każdy element na ekranie jest podświetlany niezależnie, użytkownicy muszą dużo klikać, aby poruszać się po ekranie. Jeśli elementy będą zbyt mocno ze sobą połączone, użytkownicy mogą nie zrozumieć, które elementy logicznie pasują do siebie. Jeśli na ekranie znajdują się elementy czysto dekoracyjne, można je ukryć przed usługami ułatwień dostępu. W takich przypadkach możesz użyć interfejsów API do łączenia, usuwania i ukrywania semantyki.

Semantyka scalania

Gdy zastosujesz modyfikator clickable do komponentu nadrzędnego, Compose automatycznie scali wszystkie elementy podrzędne. Aby dowiedzieć się, jak interaktywne komponenty interfejsu Compose Material i Foundation używają domyślnie strategii scalania, zapoznaj się z sekcją Elementy interaktywne.

Komponent często składa się z kilku komponentów składanych. Te komponenty mogą tworzyć logiczną grupę i każdy z nich może zawierać ważne informacje, ale nadal możesz chcieć, aby usługi ułatwień dostępu traktowały je jako jeden element.

Wyobraź sobie na przykład składankę, która zawiera awatara użytkownika, jego imię i nazwisko oraz dodatkowe informacje:

Grupa elementów interfejsu, w tym nazwa użytkownika. Wybrano nazwę.
Rysunek 1. Grupa elementów interfejsu, w tym nazwa użytkownika. Wybrano nazwę.

Aby umożliwić Compose zgrywanie tych elementów, użyj parametru mergeDescendants w modyfikatorze semantycznym. Dzięki temu usługi ułatwień dostępu traktują komponent jako jedną encję, a wszystkie właściwości semantyczne elementów potomnych są scalane:

@Composable
private fun PostMetadata(metadata: Metadata) {
    // Merge elements below for accessibility purposes
    Row(modifier = Modifier.semantics(mergeDescendants = true) {}) {
        Image(
            imageVector = Icons.Filled.AccountCircle,
            contentDescription = null // decorative
        )
        Column {
            Text(metadata.author.name)
            Text("${metadata.date}${metadata.readTimeMinutes} min read")
        }
    }
}

Usługi ułatwień dostępu skupiają się teraz na całym kontenerze i zapisują jego zawartość:

Grupa elementów interfejsu, w tym nazwa użytkownika. Wszystkie elementy są zaznaczone razem.
Rysunek 2. Grupa elementów interfejsu, w tym nazwa użytkownika. Wszystkie elementy są zaznaczone razem.

Każda właściwość semantyczna ma zdefiniowaną strategię łączenia. Na przykład właściwość ContentDescription dodaje do listy wszystkie wartości potomków ContentDescription. Aby sprawdzić strategię scalania właściwości semantycznej, sprawdź jej mergePolicyimplementację w pliku SemanticsProperties.kt. Właściwości mogą przyjmować wartość nadrzędną lub podrzędną, scalać wartości w listę lub ciąg znaków, nie zezwalać na scalanie w ogóle i zamiast tego rzucać wyjątek albo stosować inną niestandardową strategię scalania.

Istnieją też inne scenariusze, w których oczekujesz, że semantyka podrzędna zostanie scalona z semantiką nadrzędną, ale tak się nie dzieje. W tym przykładzie mamy element nadrzędny listy clickable z elementami podrzędnymi. Możemy oczekiwać, że element nadrzędny je złączy:

Element listy z obrazem, tekstem i ikoną zakładki
Rysunek 3. Element na liście z obrazem, tekstem i ikoną zakładki.

@Composable
private fun ArticleListItem(
    openArticle: () -> Unit,
    addToBookmarks: () -> Unit,
) {

    Row(modifier = Modifier.clickable { openArticle() }) {
        // Merges with parent clickable:
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Article thumbnail"
        )
        ArticleDetails()

        // Defies the merge due to its own clickable:
        BookmarkButton(onClick = addToBookmarks)
    }
}

Gdy użytkownik naciśnie element clickable Row, otworzy się artykuł. W położonym wewnątrz artykule znajduje się przycisk BookmarkButton, który pozwala dodać artykuł do zakładek. Ten zagnieżdżony przycisk wyświetla się jako niesprowadzony, podczas gdy pozostałe treści w wierszu są zredukowane:

Złączone drzewo zawiera na liście w węźle wiersza wiele tekstów. Niezłączone drzewo zawiera osobne węzły dla każdego elementu tekstowego.
Rysunek 4. Złączone drzewo zawiera kilka tekstów na liście wewnątrz węzła Row. Niezłączone drzewo zawiera osobne węzły dla każdego Text komponentu.

Niektóre komponenty nie są automatycznie scalane z elementem nadrzędnym. Element nadrzędny nie może łączyć swoich elementów podrzędnych, gdy te również się łączą, albo z ustawienia mergeDescendants = true, albo dlatego, że są komponentami, które łączą się same, np. przyciski lub elementy klikalne. Wiedza o tym, jak określone interfejsy API łączą lub odrzucają dane, może pomóc w rozwiązywaniu problemów z nieoczekiwanym zachowaniem.

Używaj scalania, gdy elementy podrzędne tworzą logiczną i sensowne grupowanie pod elementem nadrzędnym. Jeśli jednak zagnieżdżone podrzędne wymagają ręcznej zmiany lub usunięcia ich semantyki, inne interfejsy API mogą lepiej odpowiadać Twoim potrzebom (np. clearAndSetSemantics).

Wyczyść i ustaw semantykę

Jeśli informacje semantyczne muszą zostać całkowicie wyczyszczone lub zastąpione, możesz użyć potężnego interfejsu API clearAndSetSemantics.

Jeśli komponent musi usunąć własną semantykę i semantykę swoich elementów potomnych, użyj tego interfejsu API z pustą funkcją lambda. Jeśli semantyka musi zostać zastąpiona, dodaj nowe treści do funkcji lambda.

Pamiętaj, że podczas czyszczenia za pomocą pustej funkcji lambda oczyszczone semantyka nie są wysyłane do żadnego użytkownika, który korzysta z tych informacji, np. do funkcji ułatwień dostępu, autouzupełniania czy testowania. Gdy nadpisujesz zawartość za pomocą funkcji clearAndSetSemantics{/*semantic information*/}, nowa semantyka zastępuje wszystkie poprzednie semantyki elementu i jego potomków.

Poniżej znajdziesz przykład niestandardowego elementu przełącznika, który jest reprezentowany przez interaktywną pozycję z ikoną i tekstem:

// Developer might intend this to be a toggleable.
// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied,
// a custom description is set, and a Role is applied.

@Composable
fun FavoriteToggle() {
    val checked = remember { mutableStateOf(true) }
    Row(
        modifier = Modifier
            .toggleable(
                value = checked.value,
                onValueChange = { checked.value = it }
            )
            .clearAndSetSemantics {
                stateDescription = if (checked.value) "Favorited" else "Not favorited"
                toggleableState = ToggleableState(checked.value)
                role = Role.Switch
            },
    ) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null // not needed here

        )
        Text("Favorite?")
    }
}

Ikona i tekst zawierają pewne informacje semantyczne, ale razem nie wskazują, że ten element można przełączyć. Połączenie nie wystarczy, ponieważ musisz podać dodatkowe informacje o komponencie.

Ponieważ fragment kodu powyżej tworzy niestandardowy komponent przełącznika, musisz dodać możliwość przełączania, a także semantyczne elementy stateDescription, toggleableStaterole. Dzięki temu stan komponentu i powiązane działanie są dostępne. Na przykład TalkBack oznajmi: „Dodaj lub usuń zaznaczenie, klikając dwukrotnie” zamiast „Dodaj lub usuń zaznaczenie, klikając dwukrotnie”.

Po usunięciu pierwotnej semantyki i ustawieniu nowej, bardziej opisowej, usługi ułatwień dostępu mogą teraz zobaczyć, że jest to element przełączalny, który może zmieniać stan.

Podczas korzystania z funkcji clearAndSetSemantics weź pod uwagę te kwestie:

  • Ponieważ usługi nie otrzymują żadnych informacji, gdy ten interfejs API jest ustawiony, lepiej jest oszczędnie go używać.
    • Informacje semantyczne mogą być używane przez agentów AI i podobne usługi do analizowania ekranu, dlatego należy je usuwać tylko wtedy, gdy jest to konieczne.
  • Semafory niestandardowe można ustawiać w ramach funkcji lambda interfejsu API.
  • Kolejność modyfikatorów ma znaczenie – ten interfejs API usuwa całą semantykę, która jest po nim stosowana, niezależnie od innych strategii łączenia.

Ukrywanie semantyki

W niektórych przypadkach elementy nie muszą być wysyłane do usług ułatwień dostępu – być może ich dodatkowe informacje są zbędne z perspektywy ułatwień dostępu lub są tylko elementem dekoracyjnym i nieinteraktywne. W takich przypadkach możesz ukryć elementy za pomocą interfejsu API hideFromAccessibility.

W poniższych przykładach pokazano komponenty, które mogą wymagać ukrycia: nadmiarowy znak wodny obejmujący cały komponent oraz znak służący do ozdobnego oddzielania informacji.

@Composable
fun WatermarkExample(
    watermarkText: String,
    content: @Composable () -> Unit,
) {
    Box {
        WatermarkedContent()
        // Mark the watermark as hidden to accessibility services.
        WatermarkText(
            text = watermarkText,
            color = Color.Gray.copy(alpha = 0.5f),
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .semantics { hideFromAccessibility() }
        )
    }
}

@Composable
fun DecorativeExample() {
    Text(
        modifier =
        Modifier.semantics {
            hideFromAccessibility()
        },
        text = "A dot character that is used to decoratively separate information, like •"
    )
}

Użycie tutaj parametru hideFromAccessibility zapewnia, że znak wodny i ozdoba są ukryte przed usługami ułatwień dostępu, ale zachowują swoją semantykę w innych przypadkach użycia, takich jak testowanie.

Zestawienie przypadków użycia

Poniżej znajdziesz podsumowanie przypadków użycia, które pomoże Ci wyraźnie odróżnić poprzednie interfejsy API:

  • Gdy treści nie są przeznaczone do użytku przez usługi ułatwień dostępu:
    • Używaj hideFromAccessibility, gdy treści są prawdopodobnie dekoracyjne lub zbędne, ale nadal muszą być testowane.
    • Użyj clearAndSetSemantics{} z pustą funkcją lambda, gdy chcesz usunąć semantyczne relacje rodzic-dziecko we wszystkich usługach.
    • Użyj clearAndSetSemantics{/*content*/} z treścią wewnątrz funkcji lambda, gdy semantyka komponentu musi być ustawiona ręcznie.
  • Gdy treści powinny być traktowane jako jedna entyfikacja i potrzebne są wszystkie informacje o jej elementach podrzędnych:
    • Użyj semantycznych potomków z połączenia.
Tabela z różnymi przypadkami użycia interfejsu API.
Rysunek 5. Tabela z różnymi przypadkami użycia interfejsu API