💻

【Flutter】文字サイズのアクセシビリティ対応方法

2023/10/20に公開

記事の概要

Flutterアプリのテキストは、デフォルトではOSの文字サイズ設定に合わせて自動的に調整されてしまい、意図しないUIの崩れや操作性の低下が発生する。本記事では、アプリ全体に適用される文字サイズの上限値を設定し、アクセシビリティを向上する実装方法に関して解説する。
※解説にはiOSを利用します

前提知識

アクセシビリティとは

アプリ開発においてアクセシビリティ(Accessibility(A11y))とは、あらゆるユーザや状況でのアプリの機能の利用のしやすさを表す。
アプリがアクセシビリティに対応する例としては、視覚障害の方の利用を考えてアプリ内のコンテンツを音声で読み上げる機能や、文字サイズを拡大できる機能、ズーム機能を備えることや、聴覚障害の方の利用を考えてBluetoothで補聴器と連携できるようにするなどがある。
アプリ開発の際は、このように様々なユーザの利用を想定した機能の実装やUIによりアクセシビリティの向上をすることができる。
現在、世界的な基準としてはWCAG2.0(Web Content Accessibility Guidelines)というガイドラインがあり、ウェブサイトをより多くの人々がアクセスできるようにするための標準的な指針が定められている。今後公開されるWCAG3.0では、モバイルアプリに関する内容も追加される予定で、現在はドラフト版のみ公開されている。

Flutterにおける文字サイズのアクセシビリティの基本

iOSでは、OSの設定>「アクセシビリティ」>「画面表示とテキストサイズ」>「さらに大きな文字」から文字サイズの倍率値を設定することができる。
Flutterにおいて上記で設定した値は、MediaQuery.textScaleFactorOf(context)で取得することができ、これを用いて文字のサイズは論理ピクセルで決定される。

MediaQuery.textScaleFactorOf(context);

以下のサンプル実装(main.dart)ではテキストと文字サイズの倍率を表示してその動作を確認することができる。

main.dart
main.dart
import 'package:flutter/material.dart';
 
void main() {
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}
 
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('タイトル'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Column(
              children: [
                const Text(
                  '本文テスト1',
                ),
                const Text(
                  '本文テスト2',
                ),
                //OS設定の文字倍率を取得
                Text(MediaQuery.textScaleFactorOf(context).toString()),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

・倍率1.0倍に設定した場合

OS設定 アプリ画面

・倍率約3.1倍(最大)に設定した場合

OS設定 アプリ画面

上記のようにText WidgetはデフォルトでOSの設定値に依存して文字のサイズが変化する。
その仕組みはText Widgetの内部を見ることで確認できる。Text Widget内のRichTextでは以下のように実装されており、textScaleFactorが文字サイズを決めるプロパティである。

main.dart
Widget result = RichText(
      ---省略---
      textScaleFactor: textScaleFactor ?? MediaQuery.textScaleFactorOf(context),
      ---省略---
    );

textScaleFactorがnullの場合はOS設定の倍率値が適用される。
以下のような実装でその挙動を確認することができる。

main.dart
---省略---
body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Column(
              children: [
                const Text(
                  '本文テスト1',
                  // 1.0d倍で固定
                  textScaleFactor: 1.0,
                ),
                const Text(
                  '本文テスト2',
                ),
                //OS設定の文字倍率を取得
                Text(MediaQuery.textScaleFactorOf(context).toString()),
              ],
            ),
          ],
        ),
---省略---

OSの設定では倍率値を最大(約3.1倍)に設定しており、textScaleFactorを指定したText Widgetはその値が適用され、何も指定していないWidgetはOSの設定値が反映されている。

開発時に必要な対応

上記の通り、FlutterのデフォルトのText WidgetはOS設定の文字サイズに依存して変化するため、アクセシビリティを意識していないFlutterアプリでは意図せずUIが崩れてエラーが発生したり、操作性の低下につながる場合がある。
今回は、そのような問題を未然に防ぐための実装方法を解説する。

1.TextScaleFactorの上限値を制御するクラスの作成

以下のように端末のサイズ(横幅)を条件に上限値を定めておく。

text_scale_factor.dart
import 'package:flutter/material.dart';

class TextScaleFactor extends StatelessWidget {
  const TextScaleFactor({
    super.key,
    required this.child,
  });
  final Widget child;
  // 上限値
  static const _maxTextScaleFactor = 1.5;
  static const _midTextScaleFactor = 1.2;
  static const _minTextScaleFactor = 1.1;
  // 端末サイズ(横幅)
  static const _minDeviceSizeWidth = 380.0;
  static const _maxDeviceSizeWidth = 400.0;

  
  Widget build(BuildContext context) {
    final mediaQuery = MediaQuery.of(context);
    final screenWidthSize = MediaQuery.of(context).size.width;
    // 倍率の上限値
    double upperTextScaleFactor;
    // 400px以上の端末
    if (screenWidthSize >= _maxDeviceSizeWidth) {
      upperTextScaleFactor = _maxTextScaleFactor;
      // 380以上400px未満の端末
    } else if (screenWidthSize >= _minDeviceSizeWidth) {
      upperTextScaleFactor = _midTextScaleFactor;
      // 380px未満の端末
    } else {
      upperTextScaleFactor = _minTextScaleFactor;
    }
    final textScaleFactor =
        mediaQuery.textScaleFactor.clamp(0.0, upperTextScaleFactor);
    return MediaQuery(
      data: mediaQuery.copyWith(
        textScaleFactor: textScaleFactor,
      ),
      child: child,
    );
  }
}

2.アプリ全体に適用

MaterialAppのbuilderプロパティを使うことで作成した上限値をアプリ全体に適用することができる。

main.dart
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
      // アプリ全体に適用
      builder: (context, child) => TextScaleFactor(child: child!),
    );
  }
}

main.dart全文

main.dart
main.dart
import 'package:flutter/material.dart';
 
void main() {
  runApp(const MyApp());
}
 
class MyApp extends StatelessWidget {
  const MyApp({super.key});
 
  
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
      builder: (context, child) => TextScaleFactor(child: child!),
    );
  }
}
 
class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});
 
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('タイトル'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Column(
              children: [
                const Text(
                  '本文テスト1',
                ),
                const Text(
                  '本文テスト2',
                ),
                //OS設定の文字倍率
                Text(MediaQuery.textScaleFactorOf(context).toString()),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

動作確認

OSで上限値以上の値を設定しても、アプリに実装されている上限値が適用されていることが確認できる。

端末サイズ OS設定 1.0倍 OS設定 約3.1倍(最大)
375px(iphone11Pro)
390px(iphone14)
428px(iPhone12ProMax)

アプリ開発時は、あらかじめ上記のように上限値を定めておくことで、OS設定の影響による意図しないUI崩れやエラー、操作性の低下を最低限防ぐことができる。
画面の要件によっては、アプリ全体の上限値だけでは対応できない場合があるため、その場合は各WidgetごとにtextScaleFactorの制御を追加することで対応する必要がある。

参考

WINTICKET Flutter アプリと Text Scale Factor の闘い
Flutterで端末の文字サイズ設定を無視する方法

GitHubで編集を提案

Discussion