Deprecate textScaleFactor in favor of TextScaler
Summary
In preparation for adopting the Android 14 nonlinear font scaling feature,
all occurrences of textScaleFactor
in the Flutter framework have been
deprecated and replaced by TextScaler
.
Context
Many platforms allow users to scale textual contents up or down globally in
system preferences. In the past, the scaling strategy was captured as a single
double
value named textScaleFactor
, as text scaling was proportional:
scaledFontSize = textScaleFactor x unScaledFontSize
. For example, when
textScaleFactor
is 2.0 and the developer-specified font size is 14.0, the
actual font size is 2.0 x 14.0 = 28.0.
With the introduction of Android 14 nonlinear font scaling, larger text gets
scaled at a lesser rate as compared to smaller text, to prevent excessive scaling
of text that is already large. The textScaleFactor
scalar value used by
“proportional” scaling is not enough to represent this new scaling strategy.
The Replaces textScaleFactor
with TextScaler
pull request introduced a
new class TextScaler
to replace textScaleFactor
in preparation for this new
feature. Nonlinear text scaling is introduced in a different pull request.
Description of change
Introducing a new interface TextScaler
, which represents a text scaling strategy.
abstract class TextScaler {
double scale(double fontSize);
double get textScaleFactor; // Deprecated.
}
Use the scale
method to scale font sizes instead of textScaleFactor
.
The textScaleFactor
getter provides an estimated textScaleFactor
value, it
is for backward compatibility purposes and is already marked as deprecated, and
will be removed in a future version of Flutter.
The new class has replaced
double textScaleFactor
(double textScaleFactor
-> TextScaler textScaler
),
in the following APIs:
Painting library
Affected APIs | Error Message |
---|---|
InlineSpan.build({ double textScaleFactor = 1.0 }) argument |
The named parameter ‘textScaleFactor’ isn’t defined. |
TextStyle.getParagraphStyle({ double TextScaleFactor = 1.0 }) argument |
The named parameter ‘textScaleFactor’ isn’t defined. |
TextStyle.getTextStyle({ double TextScaleFactor = 1.0 }) argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
TextPainter({ double TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
TextPainter.textScaleFactor getter and setter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
TextPainter.computeWidth({ double TextScaleFactor = 1.0 }) argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
TextPainter.computeMaxIntrinsicWidth({ double TextScaleFactor = 1.0 }) argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
Rendering library
Affected APIs | Error Message |
---|---|
RenderEditable({ double TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
RenderEditable.textScaleFactor getter and setter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
RenderParagraph({ double TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
RenderParagraph.textScaleFactor getter and setter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
Widgets library
Affected APIs | Error Message |
---|---|
MediaQueryData({ double TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
MediaQueryData.textScaleFactor getter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
MediaQueryData.copyWith({ double? TextScaleFactor }) argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
MediaQuery.maybeTextScaleFactorOf(BuildContext context) static method |
‘maybeTextScaleFactorOf’ is deprecated and shouldn’t be used. |
MediaQuery.textScaleFactorOf(BuildContext context) static method |
‘textScaleFactorOf’ is deprecated and shouldn’t be used. |
RichText({ double TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
RichText.textScaleFactor getter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
Text({ double? TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
Text.rich({ double? TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
Text.textScaleFactor getter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
EditableText({ double? TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
EditableText.textScaleFactor getter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
Material library
Affected APIs | Error Message |
---|---|
SelectableText({ double? TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
SelectableText.rich({ double? TextScaleFactor = 1.0 }) constructor argument |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
SelectableText.textScaleFactor getter |
‘textScaleFactor’ is deprecated and shouldn’t be used. |
Migration guide
Widgets provided by the Flutter framework are already migrated. Migration is needed only if you’re using any of the deprecated symbols listed in the previous tables.
textScaleFactor
Migrating your APIs that expose Before:
abstract class _MyCustomPaintDelegate {
void paint(PaintingContext context, Offset offset, double textScaleFactor) {
}
}
After:
abstract class _MyCustomPaintDelegate {
void paint(PaintingContext context, Offset offset, TextScaler textScaler) {
}
}
textScaleFactor
Migrating code that consumes If you’re not currently using textScaleFactor
directly, but rather passing it
to a different API that receives a textScaleFactor
, and the receiver API has
already been migrated, then it’s relatively straightforward:
Before:
RichText(
textScaleFactor: MediaQuery.textScaleFactorOf(context),
...
)
After:
RichText(
textScaler: MediaQuery.textScalerOf(context),
...
)
If the API that provides textScaleFactor
hasn’t been migrated, consider
waiting for the migrated version.
If you wish to compute the scaled font size yourself, use TextScaler.scale
instead of the *
binary operator:
Before:
final scaledFontSize = textStyle.fontSize * MediaQuery.textScaleFactorOf(context);
After:
final scaledFontSize = MediaQuery.textScalerOf(context).scale(textStyle.fontSize);
If you are using textScaleFactor
to scale dimensions that are not font sizes,
there are no generic rules for migrating the code to nonlinear scaling, and it
might require the UI to be implemented differently. Reusing the MyTooltipBox
example:
MyTooltipBox(
size: chatBoxSize * textScaleFactor,
child: RichText(..., style: TextStyle(fontSize: 20)),
)
You could choose to use the “effective” text scale factor by applying the
TextScaler
on the font size 20: chatBoxSize * textScaler.scale(20) / 20
, or
redesign the UI and let the widget assume its own intrinsic size.
Overriding the text scaling strategy in a widget subtree
To override the existing TextScaler
used in a widget subtree, override the
MediaQuery
like so:
Before:
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 2.0),
child: child,
)
After:
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaler: _myCustomTextScaler),
child: child,
)
However, it’s rarely needed to create a custom TextScaler
subclass.
MediaQuery.withNoTextScaling
(which creates a widget that disables text scaling
altogether for its child subtree), and MediaQuery.withClampedTextScaling
(which
creates a widget that restricts the scaled font size to within the range
[minScaleFactor * fontSize, maxScaleFactor * fontSize]
), are convenience methods
that cover common cases where the text scaling strategy needs to be overridden.
Examples
Disabling Text Scaling For Icon Fonts Before:
MediaQuery(
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
child: IconTheme(
data: ..,
child: icon,
),
)
After
MediaQuery.withNoTextScaling(
child: IconTheme(
data: ...
child: icon,
),
)
Preventing Contents From Overscaling Before:
final mediaQueryData = MediaQuery.of(context);
MediaQuery(
data: mediaQueryData.copyWith(textScaleFactor: math.min(mediaQueryData.textScaleFactor, _kMaxTitleTextScaleFactor),
child: child,
)
After
MediaQuery.withClampedTextScaling(
maxScaleFactor: _kMaxTitleTextScaleFactor,
child: title,
)
Disabling Nonlinear Text Scaling
If you want to temporarily opt-out of nonlinear text scaling on Android 14 until
your app is fully migrated, put a modified MediaQuery
at the top of your app’s
widget tree:
runApp(
Builder(builder: (context) {
final mediaQueryData = MediaQuery.of(context);
final mediaQueryDataWithLinearTextScaling = mediaQueryData
.copyWith(textScaler: TextScaler.linear(mediaQueryData.textScaler.textScaleFactor));
return MediaQuery(data: mediaQueryDataWithLinearTextScaling, child: realWidgetTree);
}),
);
This trick uses the deprecated textScaleFactor
API and will stop working once
it’s removed from the Flutter API.
Timeline
Landed in version: 3.13.0-4.0.pre
In stable release: not yet (Not in 3.13)
References
API documentation:
TextScaler
MediaQuery.textScalerOf
MediaQuery.maybeTextScalerOf
MediaQuery.withNoTextScaling
MediaQuery.withClampedTextScaling
Relevant issues:
Relevant PRs: