Najważniejsze czynności dotyczące ułatwień dostępu do tworzenia wiadomości

Aby ułatwić osobom niepełnosprawnym korzystanie z Twojej aplikacji, zaprojektuj ją tak, aby spełniała najważniejsze wymagania.

Weź pod uwagę minimalne rozmiary docelowych elementów dotykowych

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

Komponenty materiałowe – takie jak Checkbox, RadioButton, Switch, Slider i Surface – ustawiają ten minimalny rozmiar wewnętrznie, ale tylko wtedy, gdy komponent może wykonywać działania użytkownika. Jeśli np. parametr Checkbox ma wartość inną niż null, pole wyboru zawiera dopełnienie, którego szerokość i wysokość muszą wynosić co najmniej 48 dp.onCheckedChange

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

Jeśli parametr onCheckedChange ma wartość null, dopełnienie nie jest uwzględniane, ponieważ nie można bezpośrednio korzystać z komponentu.

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

Rysunek 1. Pole wyboru bez dopełnienia.

Gdy implementujesz elementy sterujące wyborem, takie jak Switch, RadioButton lub Checkbox, zazwyczaj klikasz w kontenerze nadrzędnym, ustawiasz wywołanie zwrotne kliknięcia w komponencie na null i dodajesz modyfikator toggleable lub selectable do nadrzędnego elementu kompozycyjnego.

@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)
        }
    }
}

Gdy rozmiar klikalnego elementu kompozycyjnego jest mniejszy niż minimalny rozmiar docelowego elementu dotykowego, funkcja tworzenia nadal zwiększa rozmiar docelowych elementów dotykowych. Dzieje się tak, ponieważ rozmiar docelowego elementu dotykowego wykracza poza granice funkcji kompozycyjnej.

Ten przykład zawiera bardzo mały klikalny element Box. Obszar docelowego elementu dotykowego jest automatycznie rozwijany poza granice obszaru Box, więc kliknięcie obok Box nadal wywołuje zdarzenie 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)
        )
    }
}

Aby zapobiec potencjalnemu nakładaniu się obszarów dotykowych elementów kompozycyjnych, zawsze używaj odpowiednio dużego rozmiaru minimalnego dla funkcji kompozycyjnej. W tym przykładzie oznacza to użycie modyfikatora sizeIn do ustawienia minimalnego rozmiaru pola wewnętrznego:

@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)
        )
    }
}

Dodawanie etykiet kliknięć

Za pomocą etykiety kliknięcia możesz nadać elementowi kompozycyjnemu znaczenie związane z kliknięciami. Etykiety kliknięć opisują, co się dzieje, gdy użytkownik wchodzi w interakcję z komponentem. Usługi ułatwień dostępu używają etykiet kliknięć, aby opisać aplikację użytkownikom o określonych potrzebach.

Aby ustawić etykietę kliknięcia, przekaż parametr w modyfikatorze clickable:

@Composable
private fun ArticleListItem(openArticle: () -> Unit) {
    Row(
        Modifier.clickable(
            // R.string.action_read_article = "read article"
            onClickLabel = stringResource(R.string.action_read_article),
            onClick = openArticle
        )
    ) {
        // ..
    }
}

Jeśli nie masz dostępu do modyfikatora kliknięcia, możesz też ustawić etykietę kliknięcia w modyfikatorze semantyki:

@Composable
private fun LowLevelClickLabel(openArticle: () -> Boolean) {
    // R.string.action_read_article = "read article"
    val readArticleLabel = stringResource(R.string.action_read_article)
    Canvas(
        Modifier.semantics {
            onClick(label = readArticleLabel, action = openArticle)
        }
    ) {
        // ..
    }
}

Opisz elementy wizualne

Gdy zdefiniujesz element kompozycyjny Image lub Icon, platforma Androida nie może automatycznie sprawdzić, co wyświetla aplikacja. Musisz przekazać opis tekstowy elementu wizualnego.

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

Pasek ikon, które można kliknąć,

Platforma Androida nie jest w stanie opisać jej w zależności od samej ikony osobom z wadą wzroku. Platforma Androida wymaga dodatkowego opisu tekstowego ikony.

Parametr contentDescription opisuje element wizualny. Użyj zlokalizowanego ciągu znaków, tak aby był 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 wizualne mają charakter czysto dekoracyjny – użytkownik nie powinien ich przekazywać. Gdy ustawisz parametr contentDescription na null, informujesz platformę Androida, ż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)
    )
}

To Ty decydujesz, czy dany element wizualny wymaga contentDescription. Zastanów się, czy dany element przekazuje informacje, które są potrzebne do wykonania zadania. Jeśli nie, to lepiej pominąć opis.

Scal elementy

Usługi ułatwień dostępu, takie jak Talkback i Switch Access, pozwalają użytkownikom przenosić zaznaczenie między elementami na ekranie. Ważne jest, aby elementy były skoncentrowane na odpowiedniej szczegółowości. Gdy każdy element kompozycyjny niskiego poziomu na ekranie jest ustawiony niezależnie, użytkownicy muszą wchodzić w interakcję z innymi, aby poruszać się po ekranie. Jeśli elementy zbyt agresywnie łączą się ze sobą, użytkownicy mogą nie zrozumieć, które elementy razem

Gdy zastosujesz modyfikator clickable do funkcji kompozycyjnej, funkcja Utwórz automatycznie połączy wszystkie elementy, które zawiera ten element. Odnosi się to również do ListItem: elementy elementu listy scalają się, a usługi ułatwień dostępu postrzegają je jako jeden element.

Istnieje możliwość, że grupa elementów kompozycyjnych tworzy grupę logiczną, ale ta grupa nie jest klikalna ani nie wchodzi w skład elementu listy. Nadal chcesz, aby usługi ułatwień dostępu wyświetlały je jako jeden element. Na przykład wyobraźmy sobie kompozycję, która zawiera awatar użytkownika, jego nazwę i pewne dodatkowe informacje:

Grupa elementów interfejsu zawierająca nazwę użytkownika. Nazwa jest wybrana.

Możesz włączyć tworzenie, aby scalić te elementy, używając parametru mergeDescendants w modyfikatorze semantics. Dzięki temu usługi ułatwień dostępu wybierają tylko scalony element, a wszystkie właściwości semantyczne elementów podrzędnych 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, scalając zawartość:

Grupa elementów interfejsu zawierająca nazwę użytkownika. Wszystkie elementy są zaznaczone razem.

Dodaj działania niestandardowe

Przyjrzyj się tej pozycji na liście:

Typowy element listy zawierający tytuł artykułu, autora i ikonę zakładki.

Gdy używasz czytnika ekranu, takiego jak TalkBack, do słyszenia, co wyświetla się na ekranie, najpierw wybiera cały element, a następnie ikonę zakładki.

Element listy z wybranymi wszystkimi elementami razem.

Element listy z wybraną ikoną zakładki

Przy długiej liście może to być bardzo częste. Lepszym rozwiązaniem jest zdefiniowanie działania niestandardowego, które umożliwia użytkownikowi dodanie elementu do zakładek. Pamiętaj, że musisz też wyraźnie usunąć działanie ikony zakładki, aby nie została ona wybrana przez usługę ułatwień dostępu. Do tego służy modyfikator clearAndSetSemantics:

@Composable
private fun PostCardSimple(
    /* ... */
    isFavorite: Boolean,
    onToggleFavorite: () -> Boolean
) {
    val actionLabel = stringResource(
        if (isFavorite) R.string.unfavorite else R.string.favorite
    )
    Row(
        modifier = Modifier
            .clickable(onClick = { /* ... */ })
            .semantics {
                // Set any explicit semantic properties
                customActions = listOf(
                    CustomAccessibilityAction(actionLabel, onToggleFavorite)
                )
            }
    ) {
        /* ... */
        BookmarkButton(
            isBookmarked = isFavorite,
            onClick = onToggleFavorite,
            // Clear any semantics properties set on this node
            modifier = Modifier.clearAndSetSemantics { }
        )
    }
}

Opisz stan elementu

Funkcja kompozycyjna może definiować element stateDescription na potrzeby semantyki, za pomocą którego platforma Androida odczytuje stan elementu kompozycyjnego. Na przykład funkcja kompozycyjna z możliwością przełączania może mieć stan „zaznaczone” lub „odznaczone”. W niektórych przypadkach może być konieczne zastąpienie domyślnych etykiet opisu stanu używanych przez funkcję Utwórz. Możesz to zrobić, jawnie określając etykiety opisu stanu przed zdefiniowaniem funkcji kompozycyjnej jako możliwej do przełączania:

@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() }
            )
    ) {
        /* ... */
    }
}

Zdefiniuj nagłówki

Aplikacje wyświetlają czasem dużą ilość treści na jednym ekranie w kontenerze, który można przewijać. Na przykład ekran może zawierać pełną treść artykułu czytanego przez użytkownika:

Zrzut ekranu przedstawiający post na blogu z tekstem artykułu w kontenerze, który można przewijać.

Użytkownicy z ułatwieniami dostępu mają trudności z poruszaniem się po ekranie. Aby ułatwić nawigację, wskaż elementy, które są nagłówkami. W poprzednim przykładzie każdy tytuł podsekcji można zdefiniować jako nagłówek ułatwień dostępu. Niektóre usługi ułatwień dostępu, takie jak TalkBack, pozwalają użytkownikom przechodzić bezpośrednio z poziomu nagłówka do nagłówka.

Aby wskazać, że element kompozycyjny jest nagłówkiem w komponencie w komponencie, określ jego właściwość semantics:

@Composable
private fun Subsection(text: String) {
    Text(
        text = text,
        style = MaterialTheme.typography.headlineSmall,
        modifier = Modifier.semantics { heading() }
    )
}

Obsługa niestandardowych funkcji kompozycyjnych

Za każdym razem, gdy zastępujesz w aplikacji określone komponenty Material Design własnymi wersjami, musisz pamiętać o kwestiach związanych z ułatwieniami dostępu.

Załóżmy, że zastępujesz materiał Checkbox własną implementacją. Możesz zapomnieć o dodaniu modyfikatora triStateToggleable, który obsługuje właściwości ułatwień dostępu tego komponentu.

Zapoznaj się z implementacją komponentu w bibliotece Material Design i naśladuj podobne działania ułatwień dostępu. Dodatkowo w porównaniu z modyfikatorami na poziomie interfejsu należy w dużej mierze korzystać z modyfikatorów podstawowych, ponieważ zawierają one już gotowe funkcje ułatwień dostępu.

Przetestuj implementację komponentu niestandardowego za pomocą wielu usług ułatwień dostępu, by sprawdzić jej działanie.

Dodatkowe materiały