結合と消去

ユーザー補助サービスが画面上の要素を移動する際、これらの要素が適切な粒度でグループ化、分離、または非表示になっていることが重要です。画面内のすべての低レベル コンポーザブルが個別にハイライト表示されると、ユーザーは画面を移動するために多くの操作を行う必要があります。要素が過度に統合されると、どの要素が論理的に関連しているのかをユーザーが理解できなくなる可能性があります。画面上に純粋に装飾的な要素がある場合は、ユーザー補助サービスから非表示にできます。このような場合は、Compose API を使用して、セマンティクスの統合、クリア、非表示を行うことができます。

マージ セマンティクス

親コンポーザブルに clickable 修飾子を適用すると、Compose はその下にあるすべての子要素を自動的に結合します。インタラクティブな Compose Material コンポーネントと Foundation コンポーネントがデフォルトでマージ戦略を使用する方法については、インタラクティブ要素のセクションをご覧ください。

コンポーネントが複数のコンポーザブルで構成されることはよくあります。これらのコンポーザブルは論理グループを形成し、それぞれに重要な情報が含まれている可能性がありますが、ユーザー補助サービスでは 1 つの要素として表示したい場合があります。

たとえば、ユーザーのアバター、名前、追加情報を表示するコンポーザブルを考えてみましょう。

ユーザー名を含む UI 要素のグループ。名前が選択されています。
図 1. ユーザー名を含む UI 要素のグループ。名前が選択されます。

Compose でこれらの要素を結合するには、セマンティクス修飾子の mergeDescendants パラメータを使用します。これにより、ユーザー補助サービスはコンポーネントを 1 つのエンティティとして扱い、子孫のすべてのセマンティクス プロパティが統合されます。

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

ユーザー補助サービスは、コンテナ全体に一度にフォーカスし、そのコンテンツをマージするようになりました。

ユーザー名を含む UI 要素のグループ。すべての要素がまとめて選択されています。
図 2. ユーザー名を含む UI 要素のグループ。すべての要素がまとめて選択されます。

各セマンティクス プロパティには、明確なマージ戦略があります。たとえば、ContentDescription プロパティの場合、子孫の ContentDescription 値がすべてリストに追加されます。セマンティクス プロパティのマージ戦略を確認するには、SemanticsProperties.kt でその mergePolicy の実装を確認してください。プロパティでは、親または子の値を採用することも、値をリストまたは文字列にマージすることもできますが、まったくマージせずに例外をスローすることも、他のカスタムのマージ戦略を使用することもできます。

子セマンティクスが親セマンティクスにマージされることが想定されるものの、マージされないシナリオもあります。次の例では、clickable リストアイテムの親に子要素があり、親がそれらをすべて統合することが期待されます。

画像、テキスト、ブックマーク アイコンのあるリスト項目
図 3. 画像、テキスト、ブックマーク アイコンのあるリスト項目。

@Composable
private fun ArticleListItem(
    openArticle: () -> Unit,
    addToBookmarks: () -> Unit,
) {

    Row(modifier = Modifier.clickable { openArticle() }) {
        // Merges with parent clickable:
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Article thumbnail"
        )
        ArticleDetails()

        // Defies the merge due to its own clickable:
        BookmarkButton(onClick = addToBookmarks)
    }
}

ユーザーが clickable アイテム Row を押すと、記事が開きます。その中に、記事をブックマークするための BookmarkButton がネストされています。このネストされたボタンは統合されていないものとして表示されますが、行内の残りの子コンテンツは統合されます。

マージされたツリーには、行ノード内のリストに複数のテキストがある。マージされていないツリーには、テキスト コンポーザブルごとに別々のノードがある。
図 4. マージされたツリーには、Row ノード内のリストに複数のテキストがある。マージされていないツリーには、Text コンポーザブルごとに別々のノードがある。

一部のコンポーザブルは、設計上、親の下に自動的にマージされません。mergeDescendants = true を明示的に設定するか、ボタンやクリック可能な要素など、自身を結合するコンポーネントであるために、子も結合している場合、親はその子を結合できません。特定の API がどのようにマージされるか、またはマージを拒否するかを把握しておくと、予期しない動作をデバッグする際に役立ちます。

子要素が親の下で論理的かつ合理的なグループを構成する場合は、マージを使用します。ただし、ネストされた子に独自のセマンティクスの手動調整または削除が必要な場合は、他の API(clearAndSetSemantics など)の方がニーズに適している可能性があります。

セマンティクスのクリアと設定

セマンティック情報を完全に消去または上書きする必要がある場合は、clearAndSetSemantics API を使用すると便利です。

コンポーネントが独自のセマンティクスとその子孫のセマンティクスをクリアする必要がある場合は、空のラムダを指定してこの API を使用します。セマンティクスを上書きする必要がある場合は、ラムダ内に新しいコンテンツを含めます。

空のラムダでクリアすると、クリアされたセマンティクスは、この情報を使用するコンシューマー(ユーザー補助、自動入力、テストなど)に送信されません。clearAndSetSemantics{/*semantic information*/} でコンテンツを上書きすると、新しいセマンティクスが、要素とその子孫の以前のセマンティクスをすべて置き換えます。

次の例は、アイコンとテキストを含む操作可能な行で表されるカスタム切り替えコンポーネントの例です。

// Developer might intend this to be a toggleable.
// Using `clearAndSetSemantics`, on the Row, a clickable modifier is applied,
// a custom description is set, and a Role is applied.

@Composable
fun FavoriteToggle() {
    val checked = remember { mutableStateOf(true) }
    Row(
        modifier = Modifier
            .toggleable(
                value = checked.value,
                onValueChange = { checked.value = it }
            )
            .clearAndSetSemantics {
                stateDescription = if (checked.value) "Favorited" else "Not favorited"
                toggleableState = ToggleableState(checked.value)
                role = Role.Switch
            },
    ) {
        Icon(
            imageVector = Icons.Default.Favorite,
            contentDescription = null // not needed here

        )
        Text("Favorite?")
    }
}

アイコンとテキストにはセマンティック情報が含まれていますが、このコンポーネントが切り替え可能であることを示すものではありません。コンポーネントに関する追加情報を提供する必要があるため、マージだけでは不十分です。

上のスニペットはカスタム切り替えコンポーネントを作成するため、切り替え機能と、stateDescription、toggleableState、role セマンティクスを追加する必要があります。これにより、コンポーネントのステータスと関連するアクションが利用可能になります。たとえば、TalkBack は「ダブルタップして有効にする」ではなく「ダブルタップして切り替える」と読み上げます。

元のセマンティクスをクリアして、よりわかりやすい新しいセマンティクスを設定することで、ユーザー補助サービスは、これが状態を切り替えることができる切り替え可能なコンポーネントであることを認識できるようになります。

clearAndSetSemantics を使用する場合は、次の点を考慮してください。

  • この API が設定されている場合、サービスは情報を受け取らないため、使用は控えめにすることをおすすめします。
    • セマンティクス情報は、AI エージェントや同様のサービスが画面を理解するために使用される可能性があるため、必要な場合にのみクリアする必要があります。
  • カスタム セマンティクスは、API ラムダ内で設定できます。
  • 修飾子の順序は重要です。この API は、他のマージ戦略に関係なく、適用された場所より後のすべてのセマンティクスをクリアします。

セマンティクスを非表示にする

要素をユーザー補助サービスに送信する必要がない場合もあります。たとえば、追加情報がユーザー補助にとって冗長な場合や、純粋に視覚的な装飾であり、インタラクティブでない場合です。このような場合は、hideFromAccessibility API を使用して要素を非表示にできます。

次の例では、コンポーネントにまたがる冗長な透かしや、情報を装飾的に区切るために使用される文字など、非表示にする必要があるコンポーネントを示します。

@Composable
fun WatermarkExample(
    watermarkText: String,
    content: @Composable () -> Unit,
) {
    Box {
        WatermarkedContent()
        // Mark the watermark as hidden to accessibility services.
        WatermarkText(
            text = watermarkText,
            color = Color.Gray.copy(alpha = 0.5f),
            modifier = Modifier
                .align(Alignment.BottomEnd)
                .semantics { hideFromAccessibility() }
        )
    }
}

@Composable
fun DecorativeExample() {
    Text(
        modifier =
        Modifier.semantics {
            hideFromAccessibility()
        },
        text = "A dot character that is used to decoratively separate information, like •"
    )
}

ここで hideFromAccessibility を使用すると、透かしと装飾がユーザー補助サービスに表示されなくなりますが、テストなどの他のユースケースではセマンティクスが保持されます。

ユースケースの内訳

以前の API を明確に区別する方法を理解するためのユースケースの概要は次のとおりです。

  • コンテンツがユーザー補助サービスでの使用を目的としていない場合:
    • コンテンツが装飾的または冗長である可能性があるが、テストする必要がある場合は、hideFromAccessibility を使用します。
    • すべてのサービスで親と子のセマンティクスをクリアする必要がある場合は、空のラムダを指定して clearAndSetSemantics{} を使用します。
    • コンポーネントのセマンティクスを手動で設定する必要がある場合は、ラムダ内のコンテンツで clearAndSetSemantics{/*content*/} を使用します。
  • コンテンツを 1 つのエンティティとして扱う必要があり、そのすべての子の情報が完全である必要がある場合:
    • セマンティクスの子孫を統合する。
差別化された API ユースケースを示す表。
図 5. API のユースケースを区別した表。