🧍

【Flutter】アクセシビリティが高いアプリを作りたい

2024/06/28に公開

はじめに

先日のWWDCでアクセシビリティに関するセッションを視聴したりラボに参加する機会があったのですが、幅広いユーザーが使えるアプリを作っていきたいと思いつつも普段全然意識できていなかったなと反省したので基本的なところから振り返ってざっくりまとめました。

アクセシビリティとは

アクセシビリティは「アクセスしやすい」「近づきやすい」を意味する言葉で、アプリにおいてはあらゆる年齢層・身体能力を持つユーザーでも使いやすいものがアクセシビリティが高いと表現されているように思います。

iOS/Androidの両方で幅広いユーザーに使ってもらうための機能がたくさんあり、たとえば

  • コンテンンツの読み上げ
  • ズーム表示
  • 音声コントロール
  • 字幕表示

などがあります。

iOSのアクセシビリティ設定の一部

Flutterがサポートしているもの

アクセシビリティを強く意識せずにアプリを作っても、フレームワークのサポートによってある程度対応できているものがあります。
https://docs.flutter.dev/ui/accessibility-and-internationalization/accessibility

大きいフォントへの対応

Flutterのテキストウィジェットは、デバイスで設定されたフォントのサイズに基づいて自動的に計算し、アプリに反映させます。

スクリーンリーダーへの対応

スクリーンリーダーとは画面を読み上げる機能のことで、AndroidではTalkBack、iOSではVoiceOverをオンにすることで使用できます。

なぜ標準ウィジェットを使うだけでスクリーンリーダー対応ができるかというと、多くのWidgetの内部でSemanticsクラスが使用されていて、Semanticsツリーを形成しているためです。

Semanticsクラスについて

https://api.flutter.dev/flutter/widgets/Semantics-class.html
SemanticsはWidgetに説明をつけるためのクラスです。
このクラスでラップして適切なプロパティを設定することでユーザーに必要な情報を届けることができます。
以下はContainerにラベルをつける例で、スクリーンリーダー機能をオンにしていると「This is a blue container」という音声フィードバックを受け取ることができます。

Semantics(
  container: true,
  label: 'This is a blue container',
  child: Container(
    height: 100,
    width: 300,
    color: Colors.blue,
  ),
),

標準Widgetではどのように使われているのか見てみます。
https://github.com/flutter/flutter/blob/f23dbc50f05c56ef5be871203bb4f167f07df845/packages/flutter/lib/src/material/app_bar.dart#L976-L988
こちらはAppBarの実装の一部です。
excludeHeaderSemanticsSemanticsを除外するかどうかのプロパティですが、デフォルトでfalseになっているので特に何も指定しなければSemanticsを使用してtitleをスクリーンリーダーに対応させることができます。

Semanticsを関連づけるMergeSemanticsクラス

https://api.flutter.dev/flutter/widgets/MergeSemantics-class.html
MergeSemanticsでラップすると、複数のSemanticsをまとめることができます。
リンク先のドキュメントにも例として挙げられていますが、チェックボックスとテキストウィジェットが隣どうしに設置されている時、別のものとして読み上げられてしまうとユーザーの混乱を招くためMergeSemanticsでラップして関連付いていることを示す必要がありそうです。

チェックボックスとVoiceOverというテキストは関連付いていて欲しい

不要な情報を除外するExcludeSemanticsクラス

https://api.flutter.dev/flutter/widgets/ExcludeSemantics-class.html
ExcludeSemanticsクラスでラップすると、ユーザーに伝える必要のない情報を除外することができます。
たとえば以下の実装だと必要になってきそうです。

  • Stackで複数のWidgetを重ねているけどユーザーに伝えたい情報はごく一部
  • 完全に装飾のためだけに存在しておりユーザーに情報を伝える必要のないWidget

以下の動画の8:40秒あたりから、不要なSemanticsが混ざっているアプリをスクリーンリーダーを使って操作している様子が見られます。
画面をはっきりと見ることが難しくスクリーンリーダーが必要なユーザーにとって、伝えたい情報を厳選することが非常に大切なことがわかります。
https://www.youtube.com/watch?v=bWbBgbmAdQs

ちなみに、IgnorePointerを使用すると子ウィジェット以下のSemacticsは全て無視されるので、こちらは逆に伝えたい情報が含まれている場合はIgnorePointer.ignoringSemanticsをfalseにして有効にする必要があります。

アクセシビリティのテスト

アクセシビリティのガイドラインに対応できているかどうかテストできるmeetsGuidelineが用意されており、以下の項目の確認ができます。

  • androidTapTargetGuideline: Androidの最小タップ可能領域のガイドライン
  • iOSTapTargetGuideline: iOSの最小タップ可能領域のガイドライン
  • textContrastGuideline: WCAG最小テキストコントラストガイドライン
  • abeledTapTargetGuideline: ラベル付きタップターゲットガイドライン(タップ・長押しアクションにラベル付けを強制する)

全てのウィジェットをテストするのは難しいですが、共通ウィジェットを作成した時は必ずテストを通過させるようにしたりが良さそうですね。
あとは定期的にスクリーンリーダー機能をオンにして一通りアプリを触ったり、文字サイズを最大にしてちゃんと表示されているか・デザインが崩れていないか・オーバーフローが起きていないか確認するのが一番良さそうな気はします...。

おわりに

Flutterが提供している標準Widgetのおかげで、すでに作ってあるアプリのアクセシビリティ向上のための対応もそこまで工数をかけずにできそうです。(工数をかけてでもやった方がいいかとは思っています)

自分にとっては難しくない操作でも、他の人にとっては難しい操作というものは考えてみればたくさんあるはずで、今後はもっと想像力を膨らませてたくさんの人に楽しんでもらえるアプリ開発をしていけたらいいなと思います📱

GitHubで編集を提案
Altiveエンジニアリングブログ

Discussion