「モバイルアプリアクセシビリティ入門」に登場するAndroidのAPIのCompose版紹介

「モバイルアプリアクセシビリティ入門」に記載されている Android の API は View のもので、Jetpack Compose だとどのようになるのか置き換え先の紹介になります。  
あくまで API の紹介だけなので、具体的な使い方は本を読んでください。

https://gihyo.jp/book/2024/978-4-297-14602-3


## 6.3/タップ領域を確保する

`Modifier#minimumInteractiveComponentSize` が用意されており、これを適用するとシステム上で必要なタッチターゲットサイズ分のレイアウトサイズが確保されます。  
デフォルトが 48dp で設定されています。

```kotlin
Text(
    text = "Button",
    modifier = Modifier
        .clickable { ... }
        .minimumInteractiveComponentSize()
)
```

:::note info
システム上でサイズが設定される `minimumInteractiveComponentSize` ですが、Android XR では 56dp が必要なタッチターゲットサイズになるので、この API を使うと XR 上では変わる可能性がありえそうです。(Jetpack Compose for XR 1.0.0-alpha01 ではそのような変更は入っていない)
:::

https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).minimumInteractiveComponentSize()

## 6.4/contentDescription

`Icon` や `Image` のような画像を表示する Composable fun には必須の引数になっています。
読み上げが必要ない場合は null を渡すようにしましょう。

```Kotlin
Image(
    painter = painterResource(R.drawable.sample),
    contentDesctiprion = "サンプル画像"
)

Icon(
    painter = painterResource(R.drawable.sample),
    contentDesctiprion = null
)
```

`Icon` や `Image` 以外の場合は `Modifier#semantics` から `contentDescription` を設定します。

```kotlin
Text(
    text = "下の図をご覧ください",
    modifier = Modifier.semantics {
        contentDescription = "次の画像をご覧ください"
    }
)
```

:::note warn
`Modifier#semantics` による `contentDescription` の設定は Composable fun が本来持っていた読み上げ内容を上書きしてしまうため注意が必要です。
`Icon`、`Image`、`Canvas` など、記述することが本質的に困難な場合に適用しましょう。
:::

https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/SemanticsPropertyReceiver#(androidx.compose.ui.semantics.SemanticsPropertyReceiver).contentDescription()

## 6.4/importantForAccessibility

`Icon`、`Image` の場合は `contentDescription` の引数に null を渡すと読み上げさせないことができます。空文字の場合は空文字が読み上げられるので注意が必要です。
またコンポーネントに対してフォーカスを移動させずに読み上げさせない場合は `Modifier#semantics` の `invisibleToUser` を設定するか `Modifier#clearAndSetSemantic` を設定します。

```Kotlin
Icon(
    painter = painterResource(R.drawable.sample),
    contentDesctiprion = null
)

Text(
    text = "Disabled",
    modifier = Modifier.semantics {
        invisibleToUser()
    }
)

Button(
    modifier = Modifier.clearAndSetSemantics {
    }
) {
    ...
}
```



:::note warn
`Modifier#semantics` の `invisibleToUser` は Compose 1.8.0 以降 deprecated になることが予定されており、`hideFromAccessibility` に置き換わります。
名前の変更のみで挙動の差はないです。  
:::

https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/SemanticsPropertyReceiver#(androidx.compose.ui.semantics.SemanticsPropertyReceiver).invisibleToUser()

https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).clearAndSetSemantics(kotlin.Function1)

## 6.4/labelFor

compose-material や compose-material3 標準の `TextField`/`OutlinedTextField` であれば特別な設定なしに `labelFor` 相当の動きがされます。
相当の動きでしかなく `labelFor` のようにラベルとプレースホルダーを繋いで読み上げてはくれません。
また、標準の `TextField`/`OutlinedTextField` を使わない場合 Compose での API は現時点(1.7.7)でないので実装で `labelFor` 相当の読み上げの実装が必要になります。

```kotlin
var text by remember { mutableStateOf("") }
TextField(
    modifier = Modifier.fillMaxWidth(),
    value = text,
    label = { Text("名前") },
    placeholder = { Text("入力してください") },
    onValueChange = {
        text = it
    }
)
```

## 6.4/screenReaderFocusable

`Modifier.semantics(mergeDescendants = true)` で子をまとめて一括に読み上げさせることができます。


```kotlin
Column(
    modifier = Modifier.semantics(mergeDescendants = true) {}
) {
    Text("1行目")
    Text("2行目")
}
```

https://developer.android.com/reference/kotlin/androidx/compose/ui/Modifier#(androidx.compose.ui.Modifier).semantics(kotlin.Boolean,kotlin.Function1)

## 6.4/accessibilityHeading

`Modifier#semantics` で `heading()` を設定します。

```Kotlin
Column(
    modifier = Modifier.semantics {
        heading()
    }
) {
    Text("Header")
}
```

https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/SemanticsPropertyReceiver#(androidx.compose.ui.semantics.SemanticsPropertyReceiver).heading()

## 6.4/accessibilityPaneTitle

`Modifier#semantics` で `paneTitle` を設定します。

```kotlin
Dialog(
    onDismissRequest,
) {
    Column(
        Modifier.semantics {
            paneTitle = "この枠内には重要な情報が含まれています"
        }
    ) {
        ...
    }
}
```

https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/SemanticsPropertyReceiver#(androidx.compose.ui.semantics.SemanticsPropertyReceiver).paneTitle()


## 6.4/accessibilityTraversalBeforeとaccessibilityTraversalAfter

Compose では accessibilityTraversalBefore　と accessibilityTraversalAfter の代わりになる API が View よりも柔軟に対応できるようになっています。
`Modifier#semantics` から `isTraversalGroup` を設定することで要素のグルーピングをすることが可能になります。

```kotlin
Row {
    Column(Modifier.semantics { isTraversalGroup = true }) {
        Text("これは")
        Text("左のテキストです")
    }
    Column(Modifier.semantics { isTraversalGroup = true }) {
        Text("これは")
        Text("右のテキストです")
    }
}
```

`isTraversalGroup` と組み合わせて使用する `traversalIndex` で読み上げの順序を細かくカスタマイズすることができます。

```kotlin
Scaffold(
    topBar = { TopAppBar(title = { ... }) },
    floatingActionButton = {
        Box(
            modifier = Modifier.semantics { 
                isTraversalGroup = true 
                traversalIndex = -1f 
            }
        ) {
            FloatingActionButton(onClick = {}) {
                Icon(imageVector = Icons.Default.Add, contentDescription = "Add")
            }
        }
    },
) {
    ...
}
```

https://developer.android.com/develop/ui/compose/accessibility/traversal

## 6.4/accessibilityLiveRegion

`Modifier#semantics` で `accessibilityLiveRegion` 同様に `liveRegion` を設定します。

```kotlin
Text(
    modifier = Modifier.semantics {
        liveRegion = LiveRegionMode.Polite // or LiveRegionMode.Assertive
    }
)
```

https://developer.android.com/reference/kotlin/androidx/compose/ui/semantics/SemanticsPropertyReceiver#(androidx.compose.ui.semantics.SemanticsPropertyReceiver).liveRegion()


* * *

その他の本では紹介されていない Compose におけるアクセシビリティ対応の具体的な実装例は公式ドキュメントを参照することをお勧めします。

https://developer.android.com/develop/ui/compose/accessibility