結合と消去

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

マージ セマンティクス

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

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

たとえば、ユーザーのアバター、名前、追加情報を表示するコンポーザブルを想像してください。

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

セマンティクス修飾子の mergeDescendants パラメータを使用して、これらの要素を結合するように Compose を有効にできます。そうすると、ユーザー補助サービスはコンポーネントを 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 など)の方がニーズに合っている可能性があります。

セマンティクスの消去と設定

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

コンポーネントとその子孫のセマンティクスを消去する必要がある場合は、空のラムダを使用してこの 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?")
    }
}

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

上のスニペットはカスタム トグル コンポーネントを作成するため、トグル機能と stateDescriptiontoggleableStaterole セマンティクスを追加する必要があります。これにより、コンポーネントのステータスと関連するアクションが利用可能になります。たとえば、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 のユースケースをまとめた表。