💊

FlutterのTextScalerを使ったノンリニアスケーリングをiOSでもやりたい その2 AppleのDynamic Typeを再現

2023/11/19に公開

はじめに

前回はFlutterのTextScalerを使ったノンリニアスケーリングを自力でやる場合のシンプルな計算方法のものを書いた。

↓前回
https://zenn.dev/samekard_dev/articles/fb9e95e631f3a3

この記事のサイズ変換処理をもうちょっとなんとか工夫しようかと思ったが壁に当たる。

そもそも

  • サイズ26の文字が画面に表示されているとき、ユーザーが文字を大きくしたら、26をどれくらい大きくしたいと思っているか
  • サイズ14の文字が画面に表示されているとき、ユーザーが文字を小さくしたら、14をどれくらい小さくしたいと思っているか

こういうものは 状況によって違う という結論になった。サイズ26の文字が画面にいっぱいあるならサイズ26を大きくしたいのだろうし、実はサイズ17が画面を埋め尽くしているならサイズ26の方はそのままでもいいと思っているかもしれない。

また、拡大縮小はOSの設定であるので、別のアプリの使用時に設定されたものかもしれない。そうなると可能性は無限である。

で、ユーザーの変更操作に対する挙動は素直にAppleのDynamic Typeのやり方に従うのが記事として需要が高いのではないかと思いその方向で実装する。

やろうとしていることは、FlutterでTextウイジェットを作りiOSデバイス上で動かした時に、ユーザーによるフォントサイズ変更の挙動を、似たサイズのDynamic Typeの挙動と同じようにする、である。

よって、この記事の趣旨は、

FlutterのTextScalerを使ってAppleのDynamic Typeのノンリニアスケーリングを再現しよう

である。

AppleのDynamic Typeの設計を分析する

こちらの記事にまとめた。

https://zenn.dev/samekard_dev/articles/e55ebe2defc7df

図だけ貼る。

実装

エリア5のところは、

  • body が23(簡易設定最大)から53(真の最大)になったら
  • 40は60になり、body とは17差から7差
  • 34は58になり、body とは11差から5差
  • 28は56になり、body とは5差から3差

ということで body が23から53に30増えたら差が半分になるという法則で作った。60増えたら差がなくなるという式。
ただし、そのままだと設定で上げると結果が逆に小さくなるものがあるので、max()によって調整してある。

scale() 以外のコードは前回と同じであるので scale() だけ載せる。

@override
double scale(double fontSize) {
 if (fontSize == 0.0) {
   return 0.0;
 }
 //Font プログラマが設定したフォントサイズの軸はf始まり
 double fBody = 17.0; //デバイス設定が標準のときのBodyの大きさ
 double fMin = 11.0; //最低サイズ
 //Device デバイスの設定の軸の変数はd始まり
 double d4 = 17.0; //bodyのデバイス設定が標準での大きさ
 double d7 = 23.0; //bodyの(非拡張の)Max設定での大きさ
 double dDiff74 = d7 - d4;
 double dCurrent = device * d4; //今のデバイスの設定でbody(17)がいくつになっているか
 if (dCurrent < d4) {
   double dDiff = d4 - dCurrent;
   if (fontSize >= fBody) {
     return fontSize - dDiff;
   } else if (fontSize > fMin) {
     double ratio = (fontSize - fMin) / (fBody - fMin);
     return fontSize - dDiff * ratio;
   } else {
     return fontSize;
   }
 } else if (dCurrent <= d7) {
   double dDiff = dCurrent - d4;
   return fontSize + dDiff;
 } else {
   if (fontSize > fBody) {
     double dDiff = dCurrent - d7;
     double ratio1 = min(dDiff / 60.0, 1.0);
     double ratio2 = 1 - ratio1;
     double fDiff = fontSize - fBody;
     return max(fontSize + dDiff74, dCurrent + fDiff * ratio2);
   } else {
     return (fontSize + dDiff74) * dCurrent / d7;
   }
 }
}

スクリーンショット

左から
最小1、標準4、7、10
わかりにくいが今回も最小1は標準4に比べて13や11はあまり小さくなっていない。

で、どれだけ近いか

薄い青色がついている方が理想値(AppleのDynamic Typeの仕様)、その右が私のコードで計算されたフォントサイズ、その右が計算値/理想値の値。
私のコードで計算されたフォントサイズは小数点第三位以下(あった場合)をカット。
その上で計算値/理想値を出して小数点第三位以下をカット。
表の読み方は、デバイス上で最大の大きさ(AX5)に設定して、上のコードに34(LargeTitleの値)を渡すと61.5が得られ、理想値に対して1.03の近さである、と読む。

ソースコード

main.dartに全部書いてある。

https://gist.github.com/samekard-dev/ea79b8d57196c21506b29a67bb01f6e3

Discussion