API のデフォルト

Material、Compose UI、Foundation API は、多くのユーザー補助機能をデフォルトで実装し、提供しています。これらには、特定の役割と機能に従う組み込みのセマンティクスが含まれています。つまり、ほとんどのユーザー補助機能は、追加の作業をほとんどまたはまったく行わずに提供されます。

適切な目的に適切な API を使用すると、コンポーネントには通常、標準的なユースケースをカバーする事前定義されたユーザー補助機能の動作が備わっています。ただし、これらのデフォルトがアクセシビリティのニーズに合っているかどうかを必ず再確認してください。そうでない場合は、Compose でより具体的な要件に対応する方法が提供されています。

Compose API のデフォルトのユーザー補助セマンティクスとパターンを理解すると、ユーザー補助を考慮してそれらを使用できます。また、よりカスタムなコンポーネントでユーザー補助をサポートするのにも役立ちます。

タップ ターゲットの最小サイズ

クリック、タップなど、ユーザーが操作できる画面上の要素はすべて、確実に操作できるよう十分な大きさにする必要があります。これらの要素のサイズを調整する際は、マテリアル デザインのユーザー補助のガイドラインを適切に遵守するため、最小サイズを必ず 48 dp に設定してください。

マテリアル コンポーネント(CheckboxRadioButtonSwitchSliderSurface など)は、この最小サイズを内部で設定しますが、これはコンポーネントがユーザー アクションを受け取れる場合に限ります。たとえば、CheckboxonCheckedChange パラメータを非 null 値に設定すると、チェックボックスには幅と高さが 48 dp 以上のパディングが含まれます。

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

幅と高さが 48 dp のデフォルトのパディングを持つチェックボックス。
図 1. デフォルトのパディングを含むチェックボックス。

onCheckedChange パラメータを null に設定すると、コンポーネントを直接操作できないため、パディングは含まれません。

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

パディングのないチェックボックス。
図 2. パディングのないチェックボックス。

SwitchRadioButtonCheckbox などの選択コントロールを実装する場合、通常は、コンポーザブルのクリック コールバックを null に設定し、toggleable または 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)
        }
    }
}

[オプション] というテキストの横にあるチェックボックスがオンとオフに切り替わっている。
図 3. クリック可能な動作のチェックボックス。

クリック可能なコンポーザブルのサイズがタップ ターゲットの最小サイズより小さい場合、Compose はタップ ターゲットのサイズを大きくします。これは、コンポーザブルの境界の外側にタップ ターゲットのサイズを拡大することで実現されます。

次の例では、非常に小さいクリック可能な Box を作成します。タップ ターゲット領域は Box の境界を越えて自動的に拡張されるため、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)
        )
    }
}

クリック可能な小さなボックス。ボックスの横をタップすると、タップ ターゲットが拡大されます。
図 4. クリック可能な非常に小さなボックスが、より大きなタップ ターゲットに拡大されます。

異なるコンポーザブルのタップ領域が重なり合うのを防ぐため、コンポーザブルには常に十分な大きさの最小サイズを使用してください。この例では、sizeIn 修飾子を使用して内部のボックスの最小サイズを設定しています。

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

前の例の非常に小さいボックスのサイズが拡大され、より大きなタッチ ターゲットが作成されています。
図 5. ボックスのタップ ターゲットを大きくする。

グラフィック要素

Image または Icon コンポーザブルを定義する際に、アプリが何を表示しているかを Android フレームワークが自動的に認識する方法はありません。グラフィック要素のテキスト説明を渡す必要があります。

ユーザーが現在のページを友だちと共有できる画面があるとします。この画面には、クリック可能な共有アイコンがあります。

クリック可能な 4 つのアイコンのバーと、ハイライト表示された共有アイコン。
図 6. クリック可能なアイコンの行と、選択された [共有] アイコン。

Android フレームワークは、アイコンだけでは、視覚障がいのあるユーザーへの説明方法を認識できません。Android フレームワークは、アイコンに関する追加のテキスト説明を必要とします。

contentDescription パラメータは、グラフィック要素を記述します。説明はユーザーに表示されるため、ローカライズした文字列を使用します。

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

一部のグラフィック要素は純粋に装飾的なものであるため、ユーザーに説明したくない場合があります。contentDescription パラメータを null に設定すると、この要素にはアクションまたは状態が関連付けられていないことを Android フレームワークに通知できます。

@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 は、主に画像などのグラフィック要素に使用することを目的としています。ButtonText などのマテリアル コンポーネントや、clickabletoggleable などの操作可能な動作には、固有の動作を記述する他の事前定義されたセマンティクスが付属しており、他の Compose API を介して変更できます。

インタラクティブな要素

マテリアルと Foundation の Compose API は、clickabletoggleable の修飾子 API を介してユーザーが操作できる UI 要素を作成します。操作可能なコンポーネントは複数の要素で構成される可能性があるため、clickabletoggleable はデフォルトで子要素のセマンティクスをマージし、コンポーネントが 1 つの論理エンティティとして扱われるようにします。

たとえば、マテリアル Button は、子アイコンとテキストで構成されることがあります。Material Button は、子を個別に扱うのではなく、デフォルトで子のセマンティクスをマージするため、ユーザー補助サービスはそれらを適切にグループ化できます。

マージされていない子セマンティクスとマージされている子セマンティクスを持つボタン。
図 7. マージされていない子セマンティクスとマージされている子セマンティクスを持つボタン。

同様に、clickable 修飾子を使用すると、コンポーザブルは子孫のセマンティクスを単一のエンティティに統合し、対応するアクション表現とともにユーザー補助サービスに送信します。

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

親のクリック可能な要素に特定の onClickLabel を設定して、ユーザー補助サービスに追加情報を提供し、アクションのより洗練された表現を提供することもできます。

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

TalkBack を例にすると、この clickable 修飾子とそのクリックラベルにより、TalkBack は「ダブルタップして有効にする」という一般的なデフォルトのフィードバックではなく、「ダブルタップしてこの記事を開く」というアクションのヒントを提供できるようになります。

このフィードバックは、アクションの種類によって異なります。長押しすると、TalkBack のヒントとして「ダブルタップして長押しすると」というメッセージが表示され、その後にラベルが表示されます。

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

場合によっては、clickable 修飾子に直接アクセスできない(たとえば、下位のネストされたレイヤのどこかに設定されている)が、アナウンス ラベルをデフォルトから変更したいことがあります。これを行うには、semantics 修飾子を使用して、clickable の設定とアナウンスの変更を分離し、クリックラベルを設定して、アクションの表現を変更します。

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

クリック アクションを 2 回渡す必要はありません。clickableButton などの既存の Compose API が、この処理を行います。マージロジックは、存在する情報に対して最も外側の修飾子ラベルとアクションが適用されていることを確認します。前の例では、NestedArticleListItemopenArticle() のクリック アクションを clickable セマンティクスに自動的に渡します。2 番目のセマンティクス修飾子アクションでは、クリック アクションを null にすることができます。ただし、クリックラベルは最初のセマンティクス修飾子に存在しないため、2 番目のセマンティクス修飾子 onClick(label = "Open this document") から取得されます。

子セマンティクスが親セマンティクスにマージされることを期待しているのに、実際にはマージされないシナリオが発生する可能性があります。詳細については、統合とクリアをご覧ください。

カスタム コンポーネント

カスタム コンポーネントをビルドする際は、マテリアル ライブラリや他の Compose ライブラリにある類似のコンポーネントの実装を確認してください。次に、そのアクセシビリティの動作を適宜模倣または変更します。たとえば、マテリアル Checkbox を独自の実装で置き換える場合、既存の Checkbox 実装を確認することで、コンポーネントのユーザー補助プロパティを処理する triStateToggleable 修飾子を追加することを思い出せます。さらに、基盤修飾子を多用します。基盤修飾子は、このセクションで説明するユーザー補助の考慮事項と既存の Compose プラクティスに対応しているからです。

カスタム切り替えコンポーネントの例については、セマンティクスのクリアと設定のセクションをご覧ください。また、カスタム コンポーネントでユーザー補助をサポートする方法の詳細については、API ガイドラインをご覧ください。