Domyślne ustawienia interfejsu API

Interfejsy API Material, Compose UI i Foundation domyślnie implementują i oferują wiele praktyk związanych z ułatwieniami dostępu. Zawierają wbudowaną semantykę, która jest zgodna z ich konkretną rolą i funkcją. Oznacza to, że większość funkcji ułatwień dostępu jest dostępna bez dodatkowej pracy lub przy niewielkim nakładzie pracy.

Używanie odpowiednich interfejsów API do odpowiednich celów oznacza, że komponenty zwykle mają predefiniowane zachowania związane z ułatwieniami dostępu, które obejmują standardowe przypadki użycia. Zawsze jednak sprawdzaj, czy te ustawienia domyślne odpowiadają Twoim potrzebom w zakresie ułatwień dostępu. W przeciwnym razie Compose udostępnia sposoby spełnienia bardziej szczegółowych wymagań.

Znajomość domyślnej semantyki i wzorców ułatwień dostępu w interfejsach API Compose pomoże Ci korzystać z nich z uwzględnieniem ułatwień dostępu. Ułatwia też obsługę ułatwień dostępu w bardziej niestandardowych komponentach.

Minimalne rozmiary docelowych elementów dotykowych

Każdy element na ekranie, który można kliknąć, dotknąć lub w inny sposób aktywować, musi być na tyle duży, by umożliwiać skuteczną interakcję. Podczas określania rozmiaru tych elementów ustaw minimalny rozmiar na 48 dp, aby prawidłowo przestrzegać wytycznych dotyczących ułatwień dostępu w interfejsie Material Design.

Komponenty Material, takie jak Checkbox, RadioButton, Switch, SliderSurface, wewnętrznie ustawiają ten minimalny rozmiar, ale tylko wtedy, gdy komponent może odbierać działania użytkownika. Na przykład gdy element Checkbox ma parametr onCheckedChange ustawiony na wartość inną niż null, pole wyboru zawiera dopełnienie, aby mieć szerokość i wysokość co najmniej 48 dp.

@Composable
private fun CheckableCheckbox() {
    Checkbox(checked = true, onCheckedChange = {})
}

Pole wyboru z domyślnym dopełnieniem o szerokości i wysokości 48 dp.
Rysunek 1. Pole wyboru z domyślnym dopełnieniem.

Gdy parametr onCheckedChange ma wartość null, dopełnienie nie jest uwzględniane, ponieważ z komponentem nie można wchodzić w interakcję bezpośrednio.

@Composable
private fun NonClickableCheckbox() {
    Checkbox(checked = true, onCheckedChange = null)
}

Pole wyboru bez dopełnienia.
Rysunek 2. Pole wyboru bez dopełnienia.

Podczas implementowania elementów sterujących wyboru, takich jak Switch, RadioButton lub Checkbox, zwykle przenosisz zachowanie związane z klikaniem do kontenera nadrzędnego, ustawiając wywołanie zwrotne kliknięcia w funkcji kompozycyjnej na null i dodając modyfikator toggleable lub selectable do nadrzędnej funkcji kompozycyjnej.

@Composable
private fun CheckableRow() {
    MaterialTheme {
        var checked by remember { mutableStateOf(false) }
        Row(
            Modifier
                .toggleable(
                    value = checked,
                    role = Role.Checkbox,
                    onValueChange = { checked = !checked }
                )
                .padding(16.dp)
                .fillMaxWidth()
        ) {
            Text("Option", Modifier.weight(1f))
            Checkbox(checked = checked, onCheckedChange = null)
        }
    }
}

Pole wyboru obok tekstu „Opcja”, które jest zaznaczane i odznaczane.
Rysunek 3. Pole wyboru, które można kliknąć.

Gdy rozmiar klikalnego komponentu kompozycyjnego jest mniejszy niż minimalny rozmiar docelowego elementu dotykowego, Compose nadal zwiększa rozmiar docelowego elementu dotykowego. Dzieje się tak dzięki powiększeniu obszaru docelowego dotyku poza granice komponentu kompozycyjnego.

W tym przykładzie znajduje się bardzo mały klikalny element Box. Obszar docelowy dotyku jest automatycznie rozszerzany poza granice elementu Box, więc kliknięcie obok niego nadal wywołuje zdarzenie kliknięcia.Box

@Composable
private fun SmallBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .size(1.dp)
        )
    }
}

Bardzo małe klikalne pole, które można powiększyć, klikając obok niego.
Rysunek 4. Bardzo małe klikalne pole, które po kliknięciu powiększa się do większego obszaru klikalnego.

Aby zapobiec nakładaniu się obszarów dotykowych różnych komponentów, zawsze używaj wystarczająco dużego minimalnego rozmiaru komponentu. W tym przykładzie oznacza to użycie modyfikatora sizeIn do ustawienia minimalnego rozmiaru wewnętrznego pola:

@Composable
private fun LargeBox() {
    var clicked by remember { mutableStateOf(false) }
    Box(
        Modifier
            .size(100.dp)
            .background(if (clicked) Color.DarkGray else Color.LightGray)
    ) {
        Box(
            Modifier
                .align(Alignment.Center)
                .clickable { clicked = !clicked }
                .background(Color.Black)
                .sizeIn(minWidth = 48.dp, minHeight = 48.dp)
        )
    }
}

Bardzo małe pole z poprzedniego przykładu zostało powiększone, aby utworzyć większy obszar docelowy.
Rysunek 5. większy docelowy element dotykowy w postaci pola;

Elementy graficzne

Gdy zdefiniujesz funkcję kompozycyjną Image lub Icon, framework Androida nie będzie w stanie automatycznie określić, co wyświetla aplikacja. Musisz przekazać tekstowy opis elementu graficznego.

Wyobraź sobie ekran, na którym użytkownik może udostępnić bieżącą stronę znajomym. Ten ekran zawiera klikalną ikonę udostępniania:

Pasek z 4 kliknięciami ikonami, z których jedna jest wyróżniona.
Rysunek 6. Wiersz klikalnych ikon z wybraną ikoną „Udostępnij”.

Na podstawie samej ikony platforma Android nie może opisać jej użytkownikowi z wadą wzroku. Platforma Android wymaga dodatkowego opisu tekstowego ikony.

Parametr contentDescription opisuje element graficzny. Użyj zlokalizowanego ciągu znaków, ponieważ jest on widoczny dla użytkownika.

@Composable
private fun ShareButton(onClick: () -> Unit) {
    IconButton(onClick = onClick) {
        Icon(
            imageVector = Icons.Filled.Share,
            contentDescription = stringResource(R.string.label_share)
        )
    }
}

Niektóre elementy graficzne mają charakter wyłącznie dekoracyjny i nie musisz ich przekazywać użytkownikowi. Gdy ustawisz parametr contentDescription na null, poinformujesz platformę Android, że ten element nie ma powiązanych działań ani stanu.

@Composable
private fun PostImage(post: Post, modifier: Modifier = Modifier) {
    val image = post.imageThumb ?: painterResource(R.drawable.placeholder_1_1)

    Image(
        painter = image,
        // Specify that this image has no semantic meaning
        contentDescription = null,
        modifier = modifier
            .size(40.dp, 40.dp)
            .clip(MaterialTheme.shapes.small)
    )
}

contentDescription jest przeznaczony głównie do elementów graficznych, takich jak obrazy. Komponenty Material, takie jak Button czy Text, oraz działania, takie jak clickable czy toggleable, mają inne predefiniowane znaczenia, które opisują ich wewnętrzne zachowanie i można je zmieniać za pomocą innych interfejsów API Compose.

Elementy interaktywne

Interfejsy API Material i Foundation Compose udostępniają elementy interfejsu, z którymi użytkownicy mogą wchodzić w interakcję za pomocą interfejsów API modyfikatorów clickable i toggleable. Ponieważ komponenty interaktywne mogą składać się z wielu elementów, tagi clickabletoggleable domyślnie łączą semantykę elementów podrzędnych, dzięki czemu komponent jest traktowany jako jedna logiczna jednostka.

Na przykład element Button może składać się z ikony podrzędnej i tekstu. Zamiast traktować elementy podrzędne jako osobne, komponent Material Button domyślnie łączy ich semantykę, dzięki czemu usługi ułatwień dostępu mogą je odpowiednio grupować:

Przyciski z semantyką elementów podrzędnych bez scalania i ze scalaniem.
Rysunek 7. Przyciski z semantyką niepołączonych i połączonych elementów podrzędnych.

Podobnie użycie modyfikatora clickable powoduje, że komponent łączy semantykę elementów podrzędnych w jedną jednostkę, która jest wysyłana do usług ułatwień dostępu z odpowiednią reprezentacją działania:

Row(
    // Uses `mergeDescendants = true` under the hood
    modifier = Modifier.clickable { openArticle() }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open",
    )
    Text("Accessibility in Compose")
}

Możesz też ustawić konkretny onClickLabel na klikalnym elemencie nadrzędnym, aby przekazać dodatkowe informacje usługom ułatwień dostępu i zapewnić bardziej dopracowaną reprezentację działania:

Row(
    modifier = Modifier
        .clickable(onClickLabel = "Open this article") {
            openArticle()
        }
) {
    Icon(
        painter = painterResource(R.drawable.ic_logo),
        contentDescription = "Open"
    )
    Text("Accessibility in Compose")
}

Na przykład w przypadku TalkBack ten modyfikator clickable i jego etykieta kliknięcia umożliwią TalkBack podanie wskazówki dotyczącej działania „Kliknij dwukrotnie, aby otworzyć ten artykuł”, zamiast bardziej ogólnej domyślnej informacji zwrotnej „Kliknij dwukrotnie, aby aktywować”.

Informacje te zmieniają się w zależności od typu działania. Długie kliknięcie spowoduje wyświetlenie wskazówki TalkBack „Kliknij dwukrotnie i przytrzymaj, aby” wraz z etykietą:

Row(
    modifier = Modifier
        .combinedClickable(
            onLongClickLabel = "Bookmark this article",
            onLongClick = { addToBookmarks() },
            onClickLabel = "Open this article",
            onClick = { openArticle() },
        )
) {}

W niektórych przypadkach możesz nie mieć bezpośredniego dostępu do modyfikatora clickable (np. gdy jest on ustawiony w niższej warstwie zagnieżdżonej), ale nadal chcesz zmienić domyślną etykietę komunikatu. Aby to zrobić, oddziel ustawienie clickable od modyfikacji ogłoszenia, używając modyfikatora semantics i ustawiając tam etykietę kliknięcia, aby zmodyfikować reprezentację działania:

@Composable
private fun ArticleList(openArticle: () -> Unit) {
    NestedArticleListItem(
        // Clickable is set separately, in a nested layer:
        onClickAction = openArticle,
        // Semantics are set here:
        modifier = Modifier.semantics {
            onClick(
                label = "Open this article",
                action = {
                    // Not needed here: openArticle()
                    true
                }
            )
        }
    )
}

Nie musisz przekazywać działania kliknięcia dwukrotnie. Istniejące interfejsy API Compose, takie jak clickable czy Button, robią to za Ciebie. Logika łączenia sprawdza, czy w przypadku dostępnych informacji zastosowano zewnętrzną etykietę modyfikatora i działanie. W poprzednim przykładzie NestedArticleListItem automatycznie przekazuje działanie openArticle() kliknięcia do semantyki clickable. W drugim działaniu modyfikatora semantyki możesz pozostawić działanie kliknięcia jako null. Etykieta kliknięcia jest jednak pobierana z drugiego modyfikatora semantycznego onClick(label = "Open this document"), ponieważ nie występuje w pierwszym.

Może się zdarzyć, że oczekujesz scalenia semantyki elementów podrzędnych z semantyką elementu nadrzędnego, ale tak się nie dzieje. Więcej informacji znajdziesz w artykule Scalanie i czyszczenie.

Komponenty niestandardowe

Podczas tworzenia komponentu niestandardowego sprawdź implementację podobnego komponentu w bibliotece Material lub innych bibliotekach Compose. Następnie naśladuj lub zmodyfikuj jego działanie w zakresie ułatwień dostępu. Jeśli na przykład zastąpisz komponent Material Checkbox własną implementacją, sprawdzenie istniejącej implementacji Checkbox przypomni Ci o dodaniu modyfikatora triStateToggleable, który obsługuje właściwości ułatwień dostępu komponentu. Dodatkowo korzystaj w dużym stopniu z modyfikatorów Foundation, ponieważ uwzględniają one wbudowane aspekty ułatwień dostępu i dotychczasowe praktyki Compose omówione w tej sekcji.

Przykład niestandardowego komponentu przełącznika znajdziesz też w sekcji dotyczącej semantyki „Wyczyść i ustaw”. Bardziej szczegółowe informacje o tym, jak zapewnić dostępność w komponentach niestandardowych, znajdziesz w wytycznych dotyczących interfejsu API.