💊

FlutterのTextScalerを使ったノンリニアスケーリングをiOSでもやりたい その1 シンプルな処理を書く

2023/11/18に公開

はじめに

Flutterの文字の大きさ制御が、textScaleFactor から TextScaler に移行するので、少し確認したことを書く。「ユーザーが文字の大きさを大きくしても、大きい文字はさほど変わらず小さい文字こそが大きくなる」という理想をiOSデバイスで実現することを記事全体で前提にしている。そうではない人にはオーバーかも。

【結論】 iOSデバイスで自動で大きさをうまいことやってくれる設定わからず

https://docs.flutter.dev/release/breaking-changes/deprecate-textscalefactor

このページの上の方を読むと、「ユーザーが文字の大きさを大きくしても大きい文字はさほど変わらず小さい文字こそが大きくなる」という記述がしてあるので、iOSデバイスでも自動でやってくれるんだろうと思って試したが、どうも自動でやってくれる設定がわからず。

今のところの結論

デバイスの状態を取得して、自力で計算する
そのやり方を書く。

自作クラス

TextScaler のサブクラスを作った。
ここではサイズの計算をする。計算に必要なのは

  • デバイスの設定 device(ユーザーが設定した)
  • ソースコードからの指定 fontSize(開発者が指定した)

の2つ。

計算は scale メソッドで行う。引数はフォントサイズで、デバイスの設定が「標準」のときの希望サイズが渡される(少なくともこの記事ではそういう想定)。その値にデバイスの設定を考慮して計算し、計算結果を値を返す。
20.0 / fontSize で開発者の指定するフォントサイズが大きいほど反対に小さくなる値を作り、それを基に最終的な値を返す。
またユーザーが文字が小さくなるほうに設定した時は、大きい文字が積極的に小さくなるが、もともと小さい文字はそんなに変わらないようにしている。
min を使うので import 'dart:math'; してます。

class MyTextScaler extends TextScaler {
  final double device;

  const MyTextScaler({this.device = 1.0});

  @override
  double scale(double fontSize) {
    if (fontSize == 0.0) {
      return 0.0;
    }
    double ratio1;
    double ratio2;
    if (device >= 1.0) {
      ratio1 = min(20.0 / fontSize, 1.0);
      ratio2 = 1.0 + (device - 1.0) * ratio1;
    } else {
      ratio1 = min(fontSize / 20.0, 1.0);
      ratio2 = 1.0 - (1.0 - device) * ratio1;
    }
    return fontSize * ratio2;
  }

  @override
  double get textScaleFactor => device;
}

build

buildの頭でデバイスの状態を見る。これをするのにもっと良い場所があればそちらがよい。
textScaler に1.0を投げて返ってきた値がデバイスの設定だという前提で書いてある。

@override
Widget build(BuildContext context) {
  TextScaler textScaler = MediaQuery.textScalerOf(context);
  MyTextScaler myTextScaler = MyTextScaler(device: textScaler.scale(1.0));

Textウィジェット

Textウィジェットでは textScaler に自作のものを指定する。

Text(
  'Aa',
  style: const TextStyle(
    fontSize: 16,
  ),
  textScaler: myTextScaler,
),

スクリーンショット なにもしない場合

左から
最低、普通、普通+3、普通+6
10も75も同じように変化します。
もともと大きい75は右端でかなり大きくなります。

スクリーンショット 自作のTextScalerで対応した場合

左から
最低、普通、普通+3、普通+6
変化が自然に見えます。もともと大きい75は右端でも大きくなる変化が抑えられています。
わかりにくいですが普通の10から最低の10へはあまり小さくなりません。

大きくなる方にリミットを入れる

1.5倍程度を限度とするとレイアウトの楽さと機能のバランスが取れた状態になります。

@override
double scale(double fontSize) {
  if (fontSize == 0.0) {
    return 0.0;
  }
  double ratio1;
  double ratio2;
  double innerDevice = min(device, 1.5);
  if (innerDevice >= 1.0) {
    ratio1 = min(20.0 / fontSize, 1.0);
    ratio2 = min(1.0 + (innerDevice - 1.0) * ratio1, 1.5);
  } else {
    ratio1 = min(fontSize / 20.0, 1.0);
    ratio2 = 1.0 - (1.0 - innerDevice) * ratio1;
  }
  return fontSize * ratio2;
}

感想

自分で計算するなら労力は今までとさほど変わらない。
怒られる textScaleFactor から 怒られない TextScaler に変えただけとも言える(私的には)。
情報があまりないので「前提」とか「想定」という表現が多くなってしまった。

今後

何かわかったら追加します。
何か変なところがあったら指摘をお願いします。

続き

Appleの設定を再現しよう
https://zenn.dev/samekard_dev/articles/1077163a7887df

Discussion