FlutterのTextの高さがiOSとAndroidで異なる
タイトルの通り、FlutterのTextの高さがiOSとAndroidで異なる現象に遭遇したので、原因と対処法について調べてみようと思います。
前提
flutter doctor
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.19.0, on macOS 13.5.1 22G90 darwin-x64, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.2)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.2)
[✓] VS Code (version 1.92.2)
[✓] Connected device (4 available)
[✓] Network resources
• No issues found!
ソース
以下の通り、fontSize, height, fontFamilyを明示的に指定しています。
pubspec.yamlの一部:
flutter:
fonts:
- family: NotoSans
fonts:
- asset: fonts/NotoSans-Regular.ttf
- asset: fonts/NotoSans-Bold.ttf
weight: 700
main.dart:
import 'package:flutter/cupertino.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const CupertinoApp(
home: MyHomePage(title: "Home"),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static const text = 'ABCDefgh';
static const textStyle = TextStyle(
fontFamily: 'NotoSans',
fontWeight: FontWeight.w400,
fontSize: 48,
height: 48.0 / 48.0,
);
late Size calculatedTextSize;
@override
void initState() {
super.initState();
calculatedTextSize = calculateTextSize(text, textStyle);
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: const CupertinoNavigationBar(middle: Text('Home')),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Text(
text,
style: textStyle,
),
const SizedBox(height: 24),
Text(
'calculatedHeight: ${calculatedTextSize.height}',
),
],
),
),
);
}
}
Size calculateTextSize(String text, TextStyle style, { double? maxWidth, int? maxLines }) {
final textPainter = TextPainter(
text: TextSpan(
text: text,
style: style,
),
maxLines: maxLines ?? 1,
textDirection: TextDirection.ltr,
)..layout(minWidth: 0, maxWidth: maxWidth ?? double.infinity);
return textPainter.size;
}
実行結果
I expected height of the text to be 48. On iOS this code works as expected. On Android it does not works as expected, the height was 53.
OS | Height | Works as expected | Screenshot | Flutter Inspector |
---|---|---|---|---|
iOS | 48 | Yes | ![]() |
![]() |
Android10(Galaxy A7) | 53 | No | ![]() |
![]() |
Androidのデバイスの種類によって、挙動が変わるかもと思い、他のAndroidデバイスでも確認してみたところ、今度は予想に反して、上記の「Android10(GalaxyA7)」と異なり、Textの高さが期待通りの48になった。
OS | Height | Works as expected | Screenshot | Flutter Inspector |
---|---|---|---|---|
Android10(Pixel3a Emulator) | 48 | Yes | ![]() |
![]() |
実行結果まとめ
上記の実行結果をまとめると、Androidではデバイスによって、Textの高さが期待通りになったり、ならなかったりする。
原因
同じAndroid OSでも、デバイスによってフォントレンダリングエンジンに違いがあるためみたい。
- Flutterのissueのコメントによると、Flutterでは異なるプラットフォーム間もしくは異なるデバイス間で、フォントが同じように表示されることは保証できないとこと。
The font rendering differences could likely come from CoreText vs Freetype. We cannot promise identical font rendering across platforms (or even across different machines on the same platform).
https://github.com/flutter/flutter/issues/145069#issuecomment-2004580503- このissueではiOSとAndroidで同じ表示にならないと言っているけど、Androidの異なるデバイス間で表示が異なる場合についても、このコメントは当てはまると思う。
- 補足: 上記コメント内の「CoreText」はiOSの文字列を表示したりフォントを制御したりするための低レベル(低レイヤー)のインターフェイスを提供するライブラリ。「Core Text layout engine」って言っているから、フォントエンジンとも捉えられそう。
Core Text provides a low-level programming interface for laying out text and handling fonts. The Core Text layout engine is designed for high performance, ease of use, and close integration with Core Foundation.
https://developer.apple.com/documentation/coretext/ - 補足: 上記コメント内の「Freetype」は...
FreeType(フリータイプ)は、フォントエンジンを実装したオープンソースのライブラリである。
https://ja.wikipedia.org/wiki/FreeType#cite_note-3- さらに同じページによると、AndroidやiOSでも利用されているらしい。ただし、この情報のソースが2013年であることに注意が必要。
FreeBSDやAndroid、iOSなどのオペレーティングシステムにおけるフォント描画にも採用されている[3]。
- 上記のソース: https://opensource.googleblog.com/2013/05/got-cff.html
It is used for rendering on a variety of platforms including Android, Chrome OS, Linux, iOS, and many versions of Unix.
- 上記のソース: https://opensource.googleblog.com/2013/05/got-cff.html
- さらに同じページによると、AndroidやiOSでも利用されているらしい。ただし、この情報のソースが2013年であることに注意が必要。
対処法
ひとまず、Androidの場合、期待するTextの高さ(height: textStyle.fontSize! * textStyle.height!
)のSizedBoxで、Textをラップしてみる。
- 今回、高さを固定したいTextの行数が1行なので、この方法でごまかせる。
- しかし、複数行になってくると、文字の一部が欠けてしまいそう。
- その場合、AndroidのときはfontSizeまたはheightを小さくするなどの対応が必要そう。
- ただ、デバイスごとに挙動が異なるから、このやり方ではだめそう。
- 表示したいTextを何らかの方法でみえない部分に表示して、globalKeyでTextに対応するRenderBoxを取得。そこから今の高さを取得。望みの高さが得られるまで、fontSizeもしくはheightを小さくする?大変そう。
- その場合、AndroidのときはfontSizeまたはheightを小さくするなどの対応が必要そう。
- しかし、複数行になってくると、文字の一部が欠けてしまいそう。