メトロノーム開発の記録

Flutterの多言語UIを検証したら、「300 BPM」が2行に分かれていた

SANGE メトロノームで12言語・6画面サイズ・3段階の文字倍率を組み合わせた表示テストを実施。「300 BPM」の改行や長いエラー文をきっかけに、UIの修正と自動テストの確認項目を見直した記録です。

公開 更新
  • Flutter
  • 個人開発
  • 多言語対応
  • UIテスト

個人開発している「SANGE メトロノーム - シンプルなリズム練習」で、小さい画面や文字を大きくした設定での表示を検証しました。

その中で見つかったのが、お気に入りに表示する「300 BPM」の数字が「30」と「0」に分かれる問題です。画面から文字がはみ出しているわけではありません。それでも、テンポの値としては読み取りにくい表示になっていました。

レイアウトの例外が出ないことと、必要な情報を読み取れることは、それぞれ確認が必要でした。

今回は、12言語のWidgetテストとAndroidエミュレータでの画面確認を通じて、どのような表示崩れが見つかり、修正とテストへどう反映したのかをまとめます。

文字倍率130%で表示した、フランス語、ブラジルポルトガル語、インドネシア語、タイ語、ベトナム語、日本語のホーム画面下部の一覧
修正後の6言語の画面を並べた一覧です。上段左からフランス語、ブラジルポルトガル語、インドネシア語、下段左からタイ語、ベトナム語、日本語。お気に入りのドイツ語の名前は、長い名前を試すために全言語で共通にしたテストデータです。

日本語で収まっていても、同じ配置が使えるとは限らない

メトロノームの画面には、テンポの数字だけでなく、拍数、リズム名、音量、お気に入りの名前などを表示しています。保存画面では、入力欄やエラーの説明、保存・キャンセルのボタンも必要です。

日本語で確認したときには収まっていた配置でも、翻訳するとラベルの長さが変わります。たとえば、日本語の「12拍」とドイツ語の「12 Schläge」では、ボタンに必要な幅が違います。

さらに文字を大きくすると、横に並べていたものが収まらなくなります。そこで、言語だけを切り替えるのではなく、画面サイズ・文字倍率・表示する画面を組み合わせて確認することにしました。

12言語・6サイズ・3段階の文字倍率で自動検証

Widgetテストでは、次の条件を組み合わせました。Widgetテストは、Flutterの画面部品をテスト環境で描画して確認する方法です。

確認する軸条件
言語日本語、英語、スペイン語、ブラジルポルトガル語、ドイツ語、フランス語、韓国語、インドネシア語、ベトナム語、タイ語、繁体字中国語、イタリア語
論理サイズ・縦持ち320×568、360×640、360×800、393×873、412×915、432×960
文字倍率100%・130%・200%
画面ホーム、拍数、リズムの細分化、設定、言語選択、保存、お気に入り管理、お気に入り一覧
12言語 × 6サイズ × 3文字倍率 × 8画面 = 1,728条件

テストデータには、300 BPM、12拍、16分音符、音量100%、長い名前のお気に入り3件を設定しました。保存画面では、名前が重複したときのエラーも表示させています。

文字倍率は TextScaler.linear で指定しました。今回の100%・130%・200%は線形に拡大するテスト条件で、実機のOS設定による文字拡大をすべてそのまま再現したものではありません。

また、Androidエミュレータでも画像を収集しました。こちらはドイツ語、フランス語、ブラジルポルトガル語、インドネシア語、タイ語、ベトナム語、日本語の7言語が対象です。全サイズ・全倍率・全言語の総当たりではなく、ドイツ語で画面サイズと文字倍率を確認し、ほかの6言語は360×640の設定・130%で確認しました。

エミュレータでは、システムバーの分だけアプリの利用可能領域が小さくなります。設定したサイズに加えて、描画時の論理サイズとSafeArea内のサイズも記録しています。

拍数ボタンは、文字が収まる列数に変える

最初に見つかったのは、テンポの説明文や拍数の単位が右側にはみ出す問題でした。Androidエミュレータでは RenderFlex overflow も検出され、レイアウトの見直しが必要になりました。

拍数を選ぶボタンには、4列固定の配置もありました。しかし、翻訳後のラベルが長くなったり、文字倍率が上がったりすると、その列数では幅が足りなくなります。

そこで、翻訳したラベルを TextPainter で計測し、文字倍率を含めた必要な幅から列数を決めるようにしました。狭い条件では列数を減らし、縦に並べて表示します。

320×568の表示設定・ドイツ語100%で、1〜12拍の選択ボタンが2列に並んだ修正後の画面
修正後の100%表示。この条件では、12個のボタンを2列で配置できました。
320×568の表示設定・ドイツ語200%で、下部へスクロールした拍数選択画面。7〜12拍のボタンが1列に並ぶ
同じ表示サイズの200%では1列に変更。12拍のボタンと下の説明文まで、スクロールして確認しています。上の画像とは文字倍率とスクロール位置が異なり、どちらも修正後です。

ホーム画面でも、長いリズム名を狭い2列へ収める配置を見直し、必要に応じて拍数とリズムを縦並びにしました。ホームの10〜12拍の数字が分割される問題には、拍表示全体を複数行へ折り返せるようにして対応しています。

「300」が分割されても、例外になるとは限らない

画像を確認していて見つかったのが、320×568の表示設定・文字倍率200%でのお気に入りカードです。

カードを3列のまま並べていたため、「300」の途中で改行されていました。表示領域の中に文字が収まっていても、数字が「30」と「0」に分かれてしまうと、テンポの値を読み取りにくくなります。

この問題は、レイアウト例外のチェックだけでは検出できませんでした。

修正では、お気に入りカードの列数を文字倍率に合わせて調整しました。今回の小さい画面の200%表示では1列にし、テンポの数字が途中で分割されないようにしています。

320×568の表示設定・ドイツ語200%で、ホーム下部のお気に入りが1列に並び、300 BPMの数字が分割されずに表示された修正後の画面
修正後のお気に入り表示。長い名前の末尾は省略し、テンポの数字は分割せず表示しています。この条件の修正前画像は保存していないため、同条件の修正前・修正後を並べた比較ではありません。

修正と一緒に、自動テストへ「3桁のBPMが1行に収まること」という確認を追加しました。次は、その確認部分の抜粋です。

for (var card = 0; card < 3; card++) {
  final paragraph = tester.renderObject<RenderParagraph>(
    find.text('300  BPM').at(card),
  );
  expect(
    paragraph.getBoxesForSelection(
      const TextSelection(baseOffset: 0, extentOffset: 3),
    ),
    hasLength(1),
    reason:
        '$locale/$size/$scale favorite BPM must stay on one line',
  );
}

「300」に当たる文字範囲の描画ボックスが、1つになっていることを確かめています。実際のテストでは、お気に入り3件それぞれのBPMと、ホームの1〜12の拍番号を確認しました。

この方法は、今回の同じ書体で描画する数字を対象にしています。異なる言語や書体が混在する文章の改行を、一般的に判定するためのものではありません。

表示したい情報に合わせて、確認項目を具体化する。 画像から見つかった問題を、次の変更でも検出できる自動テストへ戻せたことが、今回の収穫でした。

エラーの説明と保存ボタンまで到達できるか

保存画面では、エラー文の行数制限も見直しました。

もともとは最大3行に制限していましたが、狭い画面で文字を大きくすると、長い翻訳が収まらなくなる可能性がありました。そこで、行数の上限を外し、スクロールして説明全文を読めるようにしました。

保存・キャンセルのボタンは、狭い画面や大きな文字では縦並びに変更しました。モーダルの見出しと閉じるボタン、設定名と選択中の値についても、文字に使える幅を確保する配置へ変更しています。

320×568の表示設定・ドイツ語200%で保存画面の下部へスクロールし、名前の重複エラー全文と縦並びのキャンセル・保存ボタンを表示した画面
名前が重複したときのエラーの説明と、キャンセル・保存ボタン。スクロール後の画面です。

入力中の確認も加えました。Widgetテストではキーボード相当の280論理ピクセルの下余白を設け、スクロール後に保存ボタンがその領域より上に表示されることを確認しています。エラー文については、行数制限によって省略されていないことも確認しました。

Androidエミュレータでは、実際に入力欄へフォーカスし、キーボード表示中の配置を確認しています。ただし、このとき収集したPNGはFlutter画面だけのキャプチャで、OSのキーボード自体は写りません。通常表示と入力中の画像は分けて記録しました。

修正後の結果と、確認できた範囲

2026年10月30日の検証結果は、次のとおりです。

項目結果
表示のWidgetテスト1,728条件でレイアウト例外なし。対象画面で数字の分割・エラー文の省略・保存ボタンへの到達性の確認も通過
Flutterテスト全体121件合格
静的解析指摘なし
Androidエミュレータの画面条件280条件。各実行の layoutErrors は0件
収集したPNGスクロール後・入力中を含む478枚

1,728は表示条件の組み合わせ数で、121はFlutterテスト全体の実行件数です。 テストの中で複数の表示条件を繰り返し確認しているため、別の数値として扱っています。

また、478枚は収集したPNGの枚数です。すべての画像へ個別の目視合否を付けたわけではなく、主要画面を選んで、文字の欠けや重なり、数字の分割、スクロール後の表示を確認しました。タイ語・ベトナム語も、確認した主要画面では欠字の箱表示や文字の重なりは見られませんでした。

エミュレータでは、合成サイズ6種類に加えて、Galaxy A16 5G・A06 4G・S24+・S23・Redmi 14C 4G・Pixel 9の6種類の表示設定を使いました。端末別の設定は30種類用意しましたが、今回実行したのはそのうち6種類です。

これらは、同じAndroid 14エミュレータへ解像度と検証用の近似密度を適用したものです。各メーカーの独自OSやフォント、カメラ穴を再現した検証や、30機種の実機検証は行っていません。FlutterテストはWindowsで実行し、Linux用のピクセル一致ゴールデン比較も今回は実行していません。

なお、今回の記録は表示の検証です。画面の描画や入力欄へのフォーカス、スクロールの確認結果から、音声再生や保存処理の成功までを確認したものではありません。

次のUI変更でも確かめたいこと

今回の検証で、表示テストに残したい項目が具体的になりました。

  • テンポや拍番号などの数値は、途中で分割しない。
  • エラーの説明は、全文を読めるようにする。
  • 文字を大きくしたときや入力中も、必要な操作ボタンへ到達できるようにする。

文字の大きさや翻訳に合わせて列数を変えたり、縦並びやスクロールを使ったりすることで、必要な情報と操作のための領域を確保しました。

自動テストで条件を繰り返し確認し、実際に描画した画像から気付いた問題を確認項目へ追加する。この流れを残したことで、次にUIを変更するときも、今回見つかった問題が再発していないかを確かめられるようになりました。

← 開発ブログの記事一覧へ