はじめに
こんばんは。OREOです。今回はFlutterの公式ドキュメント上で定義されているAccessibility release checklistに基づいてサンプルアプリを修正してみたいと思います。
アクセシビリティとは
アクセシビリティとは、視覚や聴覚、運動、認知などの機能に制限があるユーザーがアプリケーションを完全に操作できるようにするための機能や設計のことを指します。
Flutterにおいては以下のようなアクセシビリティ機能が提供されています。
-
スクリーンリーダー:視覚障害のあるユーザーがアプリケーションを使用できるように、画面上の要素を音声で読み上げる機能
-
ラージテキスト:ユーザーがシステム設定でテキストの大きさを変更できるようにする機能
-
高コントラスト:視覚障害のあるユーザーがアプリケーションを使用しやすくするために、高コントラストモードをサポート
Accessibility release checklistとは
Flutterの公式ドキュメントにはリリースに向けたアプリの準備における考慮事項のチェックリストが記載されています。
アクティブなインタラクション:全てのアクティブなインタラクションが何かを実行するようにします。押すことができるボタンは押された時に何かを実行するべきです。例えば、onPressedイベントにno-opコールバックがある場合, どのコントロールを押したかをスクリーン上のSnackBarで表示するように変更します。
コンテキスト切り替え:情報を入力中にユーザーのコンテキストを自動的に変更するものはありません。一般的に, ウィジェットは確認アクションなしにユーザーのコンテキストを変更することを避けるべきです。
タップ可能なターゲット:全てのタップ可能なターゲットは少なくとも48x48ピクセルであるべきです。
エラー:重要なアクションは元に戻すことができるべきです。エラーを表示するフィールドでは, 可能であれば修正を提案します。
色覚異常テスト:コントロールは色盲モードとグレースケールモードでも使用可能で読み取り可能であるべきです。
スクリーンリーダーテスト:スクリーンリーダーはタップしたときにページ上の全てのコントロールを説明でき, 説明内容は理解可能であるべきです。TalkBack(Android)とVoiceOver(iOS)でアプリをテストします。
コントラスト比:コントロールやテキストと背景の間には, 無効なコンポーネントを除いて, 少なくとも4.5:1のコントラスト比を持つことを推奨します。画像も十分なコントラストがあるか評価します。
実装してみる
Accesibility release checklistを確認しながらサンプルアプリを修正してみたいと思います。
まずこちらはサンプルアプリのソースコードです。なおコメントは削除しております。
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
アクティブなインタラクション
アクティブなインタラクション:全てのアクティブなインタラクションが何かを実行するようにします。押すことができるボタンは押された時に何かを実行するべきです。例えば、onPressedイベントにno-opコールバックがある場合, どのコントロールを押したかをスクリーン上のSnackBarで表示するように変更します。
こちらの記載に従って, 「+」押下時にSnacBarを出すように修正したいと思います。
/// 修正部のみ抜粋
FloatingActionButton(
onPressed: () {
_incrementCounter();
// Accessibility release checklist No.1
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('数を増やしました!'),
),
);
},
tooltip: 'Increment',
child: const Icon(
Icons.add,
),
),
修正前 | 修正後 |
---|---|
修正自体はこれでOKかなと思います。
コンテキスト切り替え
コンテキスト切り替え:情報を入力中にユーザーのコンテキストを自動的に変更するものはありません。一般的に, ウィジェットは確認アクションなしにユーザーのコンテキストを変更することを避けるべきです。
本記事ではできるだけシンプルに修正を行いたいのでまずは「カウンターリセット機能」を追加しようと思います。
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
void _resetCounter() {
setState(() {
_counter = 0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
onPressed: () {
_incrementCounter();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('数を増やしました!'),
),
);
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
const SizedBox(width: 20),
FloatingActionButton(
onPressed: () {
_resetCounter();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('数をリセットしました!'),
),
);
},
tooltip: 'Reset',
child: const Text('Reset'),
),
],
),
);
}
}
次に
確認アクションなしにユーザーのコンテキストを変更することを避けるべきです。
に従ってリセットボタンタップ時にアラートダイアログ表示機能を追加します。
// Resetボタンのみ抜粋
FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('確認'),
content: const Text('カウンターをリセットしますか?'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text('いいえ'),
),
TextButton(
onPressed: () {
_resetCounter();
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('数をリセットしました!'),
),
);
},
child: const Text('はい'),
),
],
);
},
);
},
tooltip: 'Reset',
child: const Text('Reset'),
),
タップ可能なターゲット
タップ可能なターゲット:全てのタップ可能なターゲットは少なくとも48x48ピクセルであるべきです。
サンプルアプリにおけるタップ可能なターゲットはFloatingActionButtonのみであり, Widget Inspectorにてボタンサイズが56x56であり,48x48以上であることを確認できたので対応不要となります。
エラー
エラー:重要なアクションは元に戻すことができるべきです。エラーを表示するフィールドでは, 可能であれば修正を提案します。
本項目のチェックリストを満たすようにするために, カウンターへの加算が一定の確率で失敗する機能を追加したいと思います。
_incrementCounter内で一定の確率でエラーを返すようにします。また実行成功時にはSnackBarを表示するようにします。
void _incrementCounter(BuildContext context) {
final random = Random();
if (random.nextInt(3) == 0) {
throw Exception('Increment failed');
}
setState(() {
_counter++;
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('数を増やしました!'),
),
);
}
本実装に合わせてFloatingActionButtonも修正します。
FloatingActionButton(
onPressed: () {
// try-catch文に変更
try {
_incrementCounter(context);
// 実行失敗時には次のアクションを促す
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('失敗しました。再実行してください。'),
),
);
}
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
さいごに
一部無駄な機能を追加しながらでしたがAccessibility release checklistに適応したサンプルアプリを作ることができました。アプリ内の色彩やスクリーンリーダー機能については後編で記載してみようと思います。