API のデフォルト

マテリアル、Compose UI、Foundation API は、デフォルトで多くのユーザー補助のベスト プラクティスを実装して提供しています。これらの要素には、特定の役割と機能に従う組み込みセマンティクスが含まれています。つまり、ほとんどのユーザー補助サポートは、追加作業なしで提供できます。

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

Compose API のデフォルトのユーザー補助セマンティクスとパターンを理解すると、ユーザー補助を念頭に置いて 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 で変更できます。

インタラクティブな要素

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

たとえば、マテリアル 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
                }
            )
        }
    )
}

この場合、既存の Compose API(clickableButton など)がクリック アクションを処理するため、クリック アクションを 2 回渡す必要はありません。これは、マージ ロジックにより、存在する情報に対して最も外側の修飾子ラベルとアクションが確実に適用されるためです。

上の例では、openArticle() クリック アクションは NestedArticleListItem によって自動的に clickable セマンティクスに深く渡され、2 番目のセマンティクス モディファイア アクションで null のままにできます。ただし、クリックラベルは最初のセマンティクス修飾子には存在しないため、2 番目のセマンティクス修飾子 onClick(label = "Open this article") から取得されます。

子セマンティクスが親セマンティクスに統合されるはずなのに、統合されないシナリオに遭遇することがあります。詳細については、マージと消去をご覧ください。

カスタム コンポーネント

カスタム コンポーネントの場合は、マテリアル ライブラリまたは他の Compose ライブラリで類似のコンポーネントの実装を確認し、ユーザー補助の動作を模倣するか、必要に応じて変更します。

たとえば、マテリアル Checkbox を独自の実装で置き換える場合、既存のチェックボックスの実装を見ると、このコンポーネントのユーザー補助プロパティを処理する triStateToggleable 修飾子を追加する必要があることがわかります。

また、基盤修飾子を多用します。基盤修飾子には、すぐに使用できるユーザー補助の考慮事項と、このセクションで説明する既存の Compose のプラクティスが含まれています。

セマンティクスの消去と設定のセクションでは、カスタム トグル コンポーネントの例を確認できます。また、カスタム コンポーネントでユーザー補助をサポートする方法について詳しくは、API ガイドラインをご覧ください。