API のデフォルト

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

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

Compose API のデフォルトのユーザー補助セマンティクスとパターンを理解すると、ユーザー補助を念頭に置いて API を使用できるようになります。また、より多くのカスタム コンポーネントでユーザー補助をサポートできるようになります。

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

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

マテリアル コンポーネント(Checkbox、RadioButton、Switch、Slider、Surface など)は、この最小サイズを内部で設定しますが、これはコンポーネントがユーザー アクションを受け取れる場合に限られます。たとえば、Checkbox の onCheckedChange パラメータが 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. パディングのないチェックボックス。

Switch、RadioButton、Checkbox などの選択コントロールを実装する場合、通常は、コンポーズ可能な要素のクリック コールバックを 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. ボックスのタップ ターゲットが大きくなりました。

要素の表示サイズを小さくする必要がある場合は、代わりにマテリアル 3 の minimumInteractiveComponentSize 修飾子を使用します。各ディメンションで少なくとも 48 dp のレイアウト スペースを予約し、そのスペースの中央に要素を配置します。これにより、要素は描画サイズを維持し、周囲の要素はスペースを空けるように移動します。この修飾子はタップ ターゲット自体を拡張するのではなく、前述の拡張のためにレイアウトにスペースを残します。Checkbox などのマテリアル コンポーネントは、この修飾子を内部で使用します。サイズ修飾子の前に配置します。サイズ修飾子が前にあると、予約できるスペースが制限されるためです。

@Composable
private fun MinimumInteractiveBox() {
    var clicked by remember { mutableStateOf(false) }
    Row(verticalAlignment = Alignment.CenterVertically) {
        Box(
            Modifier
                .clickable { clicked = !clicked }
                .minimumInteractiveComponentSize()
                .size(24.dp)
                .background(if (clicked) Color.DarkGray else Color.Black)
        )
        Text("Option")
    }
}

グラフィック要素

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

インタラクティブな要素

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

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

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

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

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

子セマンティクスが親セマンティクスに統合されることを期待しているのに、そうならない場合があります。詳細については、統合とクリアをご覧ください。

カスタム コンポーネント

カスタム コンポーネントを構築する際は、マテリアル ライブラリまたは他の Compose ライブラリにある同様のコンポーネントの実装を確認してください。次に、必要に応じて、そのユーザー補助動作を模倣または変更します。たとえば、マテリアル Checkbox を独自の実装に置き換える場合、既存の Checkbox 実装を確認すると、コンポーネントのユーザー補助プロパティを処理する triStateToggleable 修飾子を追加することを思い出せます。また、Foundation 修飾子を多用してください。これには、組み込みのユーザー補助機能に関する考慮事項と、このセクションで説明する既存の Compose プラクティスが含まれています。

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