概述

#

为了支持 Android 14 非线性字体缩放 功能,Flutter 框架中的所有 textScaleFactor 实例已被弃用并替换为 TextScaler

背景

#

许多平台允许用户在系统偏好设置中全局地放大或缩小文本内容。过去,缩放策略被捕获为一个名为 textScaleFactor 的单一 double 值,因为文本缩放是成比例的:scaledFontSize = textScaleFactor x unScaledFontSize。例如,当 textScaleFactor 为 2.0,而开发者指定的字体大小为 14.0 时,实际字体大小为 2.0 x 14.0 = 28.0。

随着 Android 14 非线性字体缩放 的引入,较大的文本缩放比例会小于较小的文本,以防止已放大的文本被过度缩放。用于“比例”缩放的 textScaleFactor 标量值不足以表示这种新的缩放策略。在 TextScaler 替换 textScaleFactor 的 pull request 中,引入了一个新类 TextScaler 来替换 textScaleFactor,为这个新功能做准备。非线性文本缩放是在另一个 pull request 中引入的。

变更说明

#

引入了一个新接口 TextScaler,它表示一种文本缩放策略。

dart
abstract class TextScaler { 
  double scale(double fontSize);
  double get textScaleFactor; // Deprecated. 
}

使用 scale 方法来缩放字体大小,而不是 textScaleFactortextScaleFactor getter 提供了一个估计的 textScaleFactor 值,它仅用于向后兼容目的,并且已标记为弃用,将在 Flutter 的未来版本中移除。

新类已替换了以下 API 中的 double textScaleFactordouble textScaleFactor -> TextScaler textScaler):

Painting 库

#
受影响的 API错误消息
InlineSpan.build({ double textScaleFactor = 1.0 }) 参数'textScaleFactor' 命名参数未定义。
TextStyle.getParagraphStyle({ double TextScaleFactor = 1.0 }) 参数'textScaleFactor' 命名参数未定义。
TextStyle.getTextStyle({ double TextScaleFactor = 1.0 }) 参数'textScaleFactor' 已弃用,不应使用。
TextPainter({ double TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
TextPainter.textScaleFactor getter 和 setter'textScaleFactor' 已弃用,不应使用。
TextPainter.computeWidth({ double TextScaleFactor = 1.0 }) 参数'textScaleFactor' 已弃用,不应使用。
TextPainter.computeMaxIntrinsicWidth({ double TextScaleFactor = 1.0 }) 参数'textScaleFactor' 已弃用,不应使用。

Rendering 库

#
受影响的 API错误消息
RenderEditable({ double TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
RenderEditable.textScaleFactor getter 和 setter'textScaleFactor' 已弃用,不应使用。
RenderParagraph({ double TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
RenderParagraph.textScaleFactor getter 和 setter'textScaleFactor' 已弃用,不应使用。

Widgets 库

#
受影响的 API错误消息
MediaQueryData({ double TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
MediaQueryData.textScaleFactor getter'textScaleFactor' 已弃用,不应使用。
MediaQueryData.copyWith({ double? TextScaleFactor }) 参数'textScaleFactor' 已弃用,不应使用。
MediaQuery.maybeTextScaleFactorOf(BuildContext context) 静态方法'maybeTextScaleFactorOf' 已弃用,不应使用。
MediaQuery.textScaleFactorOf(BuildContext context) 静态方法'textScaleFactorOf' 已弃用,不应使用。
RichText({ double TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
RichText.textScaleFactor getter'textScaleFactor' 已弃用,不应使用。
Text({ double? TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
Text.rich({ double? TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
Text.textScaleFactor getter'textScaleFactor' 已弃用,不应使用。
EditableText({ double? TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
EditableText.textScaleFactor getter'textScaleFactor' 已弃用,不应使用。

Material 库

#
受影响的 API错误消息
SelectableText({ double? TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
SelectableText.rich({ double? TextScaleFactor = 1.0 }) 构造函数参数'textScaleFactor' 已弃用,不应使用。
SelectableText.textScaleFactor getter'textScaleFactor' 已弃用,不应使用。

迁移指南

#

Flutter 框架提供的 widget 已迁移。仅在您使用上表中列出的任何已弃用符号时才需要迁移。

迁移暴露 textScaleFactor 的 API

#

之前

dart
abstract class _MyCustomPaintDelegate { 
  void paint(PaintingContext context, Offset offset, double textScaleFactor) { 
  }
}

之后

dart
abstract class _MyCustomPaintDelegate { 
  void paint(PaintingContext context, Offset offset, TextScaler textScaler) { 
  }
}

迁移使用 textScaleFactor 的代码

#

如果您当前不直接使用 textScaleFactor,而是将其传递给接收 textScaleFactor 的其他 API,并且接收 API 已迁移,那么迁移相对简单。

之前

dart
RichText( 
  textScaleFactor: MediaQuery.textScaleFactorOf(context),
  ...
)

之后

dart
RichText( 
  textScaler: MediaQuery.textScalerOf(context),
  ...
)

如果提供 textScaleFactor 的 API 未迁移,请考虑等待迁移版本。

如果您希望自己计算缩放后的字体大小,请使用 TextScaler.scale,而不是 * 二元运算符。

之前

dart
final scaledFontSize = textStyle.fontSize * MediaQuery.textScaleFactorOf(context);

之后

dart
final scaledFontSize = MediaQuery.textScalerOf(context).scale(textStyle.fontSize);

如果您使用 textScaleFactor 来缩放非字体大小的尺寸,没有通用的规则来将代码迁移到非线性缩放,这可能需要重新实现 UI。重用 MyTooltipBox 示例

dart
MyTooltipBox( 
  size: chatBoxSize * textScaleFactor,
  child: RichText(..., style: TextStyle(fontSize: 20)),
)

您可以选择使用“有效”文本比例因子,方法是将 TextScaler 应用于字体大小 20:chatBoxSize * textScaler.scale(20) / 20,或者重新设计 UI,让 widget 假定其自身的内在大小。

在 widget 子树中覆盖文本缩放策略

#

要覆盖 widget 子树中使用的现有 TextScaler,请像这样覆盖 MediaQuery

之前

dart
MediaQuery( 
  data: MediaQuery.of(context).copyWith(textScaleFactor: 2.0),
  child: child,
)

之后

dart
MediaQuery( 
  data: MediaQuery.of(context).copyWith(textScaler: _myCustomTextScaler),
  child: child,
)

然而,创建自定义 TextScaler 子类的情况很少。MediaQuery.withNoTextScaling(创建一个为子 widget 子树禁用文本缩放的 widget)和 MediaQuery.withClampedTextScaling(创建一个将缩放后的字体大小限制在 [minScaleFactor * fontSize, maxScaleFactor * fontSize] 范围内的 widget)是方便的方法,它们涵盖了需要覆盖文本缩放策略的常见情况。

示例

#

为图标字体禁用文本缩放

之前

dart
MediaQuery(
  data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
  child: IconTheme(
    data: ..,
    child: icon,
  ),
)

之后

dart
MediaQuery.withNoTextScaling(
  child: IconTheme(
    data: ...
    child: icon,
  ),
)

防止内容过度缩放

之前

dart
final mediaQueryData = MediaQuery.of(context);
MediaQuery(
  data: mediaQueryData.copyWith(textScaleFactor: math.min(mediaQueryData.textScaleFactor, _kMaxTitleTextScaleFactor),
  child: child,
)

之后

dart
MediaQuery.withClampedTextScaling(
  maxScaleFactor: _kMaxTitleTextScaleFactor,
  child: title,
)

禁用非线性文本缩放

如果您想暂时退出 Android 14 的非线性文本缩放,直到您的应用完全迁移,请在应用 widget 树的顶部放置一个修改后的 MediaQuery

dart
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);
  }),
);

这个技巧使用了已弃用的 textScaleFactor API,一旦它从 Flutter API 中移除,将不再起作用。

时间线

#

已发布版本:3.13.0-4.0.pre
稳定版本:3.16

参考资料

#

API 文档

相关问题

相关 PR