Domyślne ustawienia interfejsu API

Interfejsy API Material, Compose UI i Foundation implementują i oferują domyślnie wiele praktyk ułatwień dostępu. Zawierają one wbudowaną semantykę, która odpowiada ich roli i funkcji, co oznacza, że większość ułatwień jest zapewniana bez konieczności dodatkowej pracy.

Korzystanie z odpowiednich interfejsów API do odpowiednich celów zwykle oznacza, że komponenty mają wstępnie zdefiniowane zachowania ułatwień dostępu, które obejmują standardowe przypadki użycia. Pamiętaj jednak, aby sprawdzić, czy te domyślne ustawienia odpowiadają Twoim potrzebom w zakresie ułatwień dostępu. Jeśli nie, Compose oferuje też sposoby na spełnienie bardziej szczegółowych wymagań.

Znajomość domyślnej semantyki ułatwia zrozumienie, jak używać interfejsów API Compose z uwzględnieniem ułatwień dostępu, a także jak obsługiwać ułatwienia dostępu w większej liczbie komponentów niestandardowych.

Minimalne rozmiary docelowych obszarów kliknięcia

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

Składniki Material Design, takie jak Checkbox, RadioButton, Switch, Slider i Surface, ustawiają tę minimalną wielkość wewnętrznie, 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 wypełnienie, aby jego szerokość i wysokość wynosiły co najmniej 48 dp.

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

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

Jeśli parametr onCheckedChange ma wartość null, wypełnienie nie jest uwzględniane, ponieważ nie można bezpośrednio wchodzić w interakcję z elementem.

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

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

Podczas implementowania elementów sterujących wyborem, takich jak Switch, RadioButton lub Checkbox, przenosisz zazwyczaj możliwość kliknięcia do nadrzędnego kontenera, ustawiając w funkcji kompozytowanej wartość null i dodając do niej modyfikator toggleable lub selectable.

@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” jest zaznaczone i odznaczone.
Rysunek 3. Pole wyboru z możliwością kliknięcia.

Jeśli rozmiar kompozytywny, który można kliknąć, jest mniejszy niż minimalny rozmiar docelowego elementu dotykowego, funkcja Compose zwiększa rozmiar docelowego elementu dotykowego. Dzieje się tak, gdy rozmiar docelowy dotykowy wykracza poza granice kompozytu.

Ten przykład zawiera bardzo mały przycisk Box, który można kliknąć. Obszar dotykowy jest automatycznie rozszerzany poza granice elementu Box, więc kliknięcie obok elementu Box nadal powoduje wywołanie zdarzenia kliknięcia.

@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 pole, które po kliknięciu obok staje się większym punktem dotykowym.
Rysunek 4. Bardzo małe klikalne pole, które po rozwinięciu staje się większym obiektem dotykowym.

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, aby ustawić minimalny rozmiar 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 stworzyć większy obszar dotykowy.
Rysunek 5. Większy dotykowy element docelowy w kształcie pola.

Elementy graficzne

Gdy definiujesz komponent Image lub Icon, platforma Android nie ma automatycznego sposobu na zrozumienie, co aplikacja wyświetla. Musisz podać tekstowy opis elementu graficznego.

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

Pasek z 4 klikalnymi ikonami, z wyróżnioną ikoną „Udostępnij”.
Rysunek 6. Wiersz klikalnych ikon z wybraną ikoną „Udostępnij”.

Na podstawie samej ikony framework Androida nie może opisać jej użytkownikowi niedowidzącego. Platforma Android wymaga dodatkowego opisu 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 czysto dekoracyjny i możesz nie chcieć przekazywać ich 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. Składniki materiału, takie jak Button lub Text, oraz interaktywne zachowania, takie jak clickable lub toggleable, są dostarczane z innymi zdefiniowanymi semantycznie elementami, które opisują ich zachowanie. Można je zmieniać za pomocą innych interfejsów API Compose.

Elementy interaktywne

Interfejsy API Material i Foundation Compose umożliwiają tworzenie elementów interfejsu użytkownika, z którymi użytkownicy mogą wchodzić w interakcje za pomocą interfejsów modyfikatora clickabletoggleable. Ponieważ komponenty interaktywne mogą składać się z wielu elementów, komponenty clickabletoggleable domyślnie scalają semantykę swoich elementów podrzędnych, dzięki czemu komponent jest traktowany jako jedna jednostka logiczna.

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

Przyciski z niezłączoną i złączoną semantyką elementów podrzędnych.
Rysunek 7. Przyciski z niezłączoną i złączoną semantyką elementów podrzędnych.

Podobnie modyfikator clickable powoduje, że kompozybilny element scala semantykę swoich potomków w jeden element, który jest wysyłany 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 element onClickLabel w przypadku elementów klikalnych nadrzędnych, aby przekazać dodatkowe informacje usługom ułatwień dostępu i zapewnić bardziej dopracowane przedstawienie działania:

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

W przypadku TalkBacka ten modyfikator clickable i jego etykieta kliknięcia umożliwiłyby TalkBackowi wyświetlanie wskazówki „Kliknij dwukrotnie, aby otworzyć ten artykuł”, zamiast ogólnego domyślnego komunikatu „Kliknij dwukrotnie, aby aktywować”.

Te informacje różnią się w zależności od typu działania. Długie kliknięcie wyświetli podpowiedź TalkBack „Dwa razy dotknij i przytrzymaj, aby” oraz 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 (na przykład gdy jest on ustawiony gdzieś w niższej warstwie), ale mimo to chcesz zmienić etykietę komunikatu z domyślnej. Aby to zrobić, oddziel ustawienie clickable od modyfikacji ogłoszenia za pomocą modyfikatora semantics i ustawienia etykiety 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
                }
            )
        }
    )
}

W tym przypadku nie musisz przekazywać akcji kliknięcia dwukrotnie, ponieważ istniejące interfejsy Compose API, takie jak clickable czy Button, robią to za Ciebie. Dzieje się tak, ponieważ logika scalania zapewnia, że dla obecnych informacji są podejmowane działania dotyczące zewnętrznej etykiety modyfikatora i działania.

W poprzednim przykładzie działanie kliknięcia openArticle() jest przekazywane przez NestedArticleListItem automatycznie do semantyki clickable i może być puste w drugim działaniu modyfikatora semantyki. Etykieta kliknięcia jest jednak pobierana z drugiego modyfikatora semantycznego (onClick(label = "Open this article")), ponieważ nie była obecna w pierwszym.

Możesz napotkać sytuacje, w których oczekujesz, że semantyka podrzędna zostanie scalona z elementem nadrzędnym, ale tak się nie dzieje. Więcej informacji znajdziesz w sekcji Łączenie i czyszczenie.

Komponenty niestandardowe

W przypadku komponentów niestandardowych warto przyjrzeć się implementacji podobnego komponentu w bibliotece Material lub innych bibliotekach Compose i naśladować lub modyfikować jego zachowanie w zakresie ułatwień dostępu, gdy jest to uzasadnione.

Jeśli na przykład zamierzasz zastąpić komponent Checkbox własną implementacją, sprawdzenie istniejącej implementacji pola wyboru przypomni Ci, aby dodać modyfikator triStateToggleable, który obsługuje właściwości ułatwień dostępu tego komponentu.

Dodatkowo warto często używać modyfikatorów Foundation, ponieważ uwzględniają one standardowe kwestie dotyczące dostępności, a także istniejące praktyki dotyczące Compose opisane w tej sekcji.

Przykład niestandardowego komponentu przełącznika znajdziesz w sekcji Wyraźne i ukryte semantyka, a więcej szczegółowych informacji o wsparciu dostępności w komponentach niestandardowych znajdziesz w wytycznych dotyczących interfejsu API.