lerp static method Null safety

TextStyle? lerp(
  1. TextStyle? a,
  2. TextStyle? b,
  3. double t
)

Interpolate between two text styles for animated transitions.

Interpolation will not work well if the styles don't specify the same fields. When this happens, to keep the interpolated transition smooth, the implementation uses the non-null value throughout the transition for lerpable fields such as colors (for example, if one TextStyle specified fontSize but the other didn't, the returned TextStyle will use the fontSize from the TextStyle that specified it, regarless of the t value).

This method throws when the given TextStyles don't have the same inherit value and a lerpable field is missing from both TextStyles, as that could result in jumpy transitions.

The t argument represents position on the timeline, with 0.0 meaning that the interpolation has not started, returning a (or something equivalent to a), 1.0 meaning that the interpolation has finished, returning b (or something equivalent to b), and values in between meaning that the interpolation is at the relevant point on the timeline between a and b. The interpolation can be extrapolated beyond 0.0 and 1.0, so negative values and values greater than 1.0 are valid (and can easily be generated by curves such as Curves.elasticInOut).

Values for t are usually obtained from an Animation<double>, such as an AnimationController.

If foreground is specified on either of a or b, both will be treated as if they have a foreground paint (creating a new Paint if necessary based on the color property).

If background is specified on either of a or b, both will be treated as if they have a background paint (creating a new Paint if necessary based on the backgroundColor property).

Implementation

static TextStyle? lerp(TextStyle? a, TextStyle? b, double t) {
  assert(t != null);
  if (a == null && b == null) {
    return null;
  }

  String? lerpDebugLabel;
  assert(() {
    lerpDebugLabel = 'lerp(${a?.debugLabel ?? _kDefaultDebugLabel} ⎯${t.toStringAsFixed(1)}→ ${b?.debugLabel ?? _kDefaultDebugLabel})';
    return true;
  }());

  if (a == null) {
    return TextStyle(
      inherit: b!.inherit,
      color: Color.lerp(null, b.color, t),
      backgroundColor: Color.lerp(null, b.backgroundColor, t),
      fontSize: t < 0.5 ? null : b.fontSize,
      fontWeight: FontWeight.lerp(null, b.fontWeight, t),
      fontStyle: t < 0.5 ? null : b.fontStyle,
      letterSpacing: t < 0.5 ? null : b.letterSpacing,
      wordSpacing: t < 0.5 ? null : b.wordSpacing,
      textBaseline: t < 0.5 ? null : b.textBaseline,
      height: t < 0.5 ? null : b.height,
      leadingDistribution: t < 0.5 ? null : b.leadingDistribution,
      locale: t < 0.5 ? null : b.locale,
      foreground: t < 0.5 ? null : b.foreground,
      background: t < 0.5 ? null : b.background,
      shadows: t < 0.5 ? null : b.shadows,
      fontFeatures: t < 0.5 ? null : b.fontFeatures,
      fontVariations: t < 0.5 ? null : b.fontVariations,
      decoration: t < 0.5 ? null : b.decoration,
      decorationColor: Color.lerp(null, b.decorationColor, t),
      decorationStyle: t < 0.5 ? null : b.decorationStyle,
      decorationThickness: t < 0.5 ? null : b.decorationThickness,
      debugLabel: lerpDebugLabel,
      fontFamily: t < 0.5 ? null : b._fontFamily,
      fontFamilyFallback: t < 0.5 ? null : b._fontFamilyFallback,
      package: t < 0.5 ? null : b._package,
      overflow: t < 0.5 ? null : b.overflow,
    );
  }

  if (b == null) {
    return TextStyle(
      inherit: a.inherit,
      color: Color.lerp(a.color, null, t),
      backgroundColor: Color.lerp(null, a.backgroundColor, t),
      fontSize: t < 0.5 ? a.fontSize : null,
      fontWeight: FontWeight.lerp(a.fontWeight, null, t),
      fontStyle: t < 0.5 ? a.fontStyle : null,
      letterSpacing: t < 0.5 ? a.letterSpacing : null,
      wordSpacing: t < 0.5 ? a.wordSpacing : null,
      textBaseline: t < 0.5 ? a.textBaseline : null,
      height: t < 0.5 ? a.height : null,
      leadingDistribution: t < 0.5 ? a.leadingDistribution : null,
      locale: t < 0.5 ? a.locale : null,
      foreground: t < 0.5 ? a.foreground : null,
      background: t < 0.5 ? a.background : null,
      shadows: t < 0.5 ? a.shadows : null,
      fontFeatures: t < 0.5 ? a.fontFeatures : null,
      fontVariations: t < 0.5 ? a.fontVariations : null,
      decoration: t < 0.5 ? a.decoration : null,
      decorationColor: Color.lerp(a.decorationColor, null, t),
      decorationStyle: t < 0.5 ? a.decorationStyle : null,
      decorationThickness: t < 0.5 ? a.decorationThickness : null,
      debugLabel: lerpDebugLabel,
      fontFamily: t < 0.5 ? a._fontFamily : null,
      fontFamilyFallback: t < 0.5 ? a._fontFamilyFallback : null,
      package: t < 0.5 ? a._package : null,
      overflow: t < 0.5 ? a.overflow : null,
    );
  }

  assert(() {
    if (a.inherit == b.inherit) {
      return true;
    }

    final List<String> nullFields = <String>[
      if (a.foreground == null && b.foreground == null && a.color == null && b.color == null) 'color',
      if (a.background == null && b.background == null && a.backgroundColor == null && b.backgroundColor == null) 'backgroundColor',
      if (a.fontSize == null && b.fontSize == null) 'fontSize',
      if (a.letterSpacing == null && b.letterSpacing == null) 'letterSpacing',
      if (a.wordSpacing == null && b.wordSpacing == null) 'wordSpacing',
      if (a.height == null && b.height == null) 'height',
      if (a.decorationColor == null && b.decorationColor == null) 'decorationColor',
      if (a.decorationThickness == null && b.decorationThickness == null) 'decorationThickness',
    ];
    if (nullFields.isEmpty) {
      return true;
    }

    throw FlutterError.fromParts(<DiagnosticsNode>[
      ErrorSummary('Failed to interpolate TextStyles with different inherit values.'),
      ErrorSpacer(),
      ErrorDescription('The TextStyles being interpolated were:'),
      a.toDiagnosticsNode(name: 'from', style: DiagnosticsTreeStyle.singleLine),
      b.toDiagnosticsNode(name: 'to', style: DiagnosticsTreeStyle.singleLine),
      ErrorDescription(
        'The following fields are unspecified in both TextStyles:\n'
        '${nullFields.map((String name) => '"$name"').join(', ')}.\n'
        'When "inherit" changes during the transition, these fields may '
        'observe abrupt value changes as a result, causing "jump"s in the '
        'transition.'
      ),
      ErrorSpacer(),
      ErrorHint(
        'In general, TextStyle.lerp only works well when both TextStyles have '
        'the same "inherit" value, and specify the same fields.',
      ),
      ErrorHint(
        'If the TextStyles were directly created by you, consider bringing '
        'them to parity to ensure a smooth transition.'
      ),
      ErrorSpacer(),
      ErrorHint(
        'If one of the TextStyles being lerped is significantly more elaborate '
        'than the other, and has "inherited" set to false, it is often because '
        'it is merged with another TextStyle before being lerped. Comparing '
        'the "debugLabel"s of the two TextStyles may help identify if that was '
        'the case.'
      ),
      ErrorHint(
        'For example, you may see this error message when trying to lerp '
        'between "ThemeData()" and "Theme.of(context)". This is because '
        'TextStyles from "Theme.of(context)" are merged with TextStyles from '
        'another theme and thus are more elaborate than the TextStyles from '
        '"ThemeData()" (which is reflected in their "debugLabel"s -- '
        'TextStyles from "Theme.of(context)" should have labels in the form of '
        '"(<A TextStyle>).merge(<Another TextStyle>)"). It is recommended to '
        'only lerp ThemeData with matching TextStyles.'
      ),
    ]);
  }());

  return TextStyle(
    inherit: t < 0.5 ? a.inherit : b.inherit,
    color: a.foreground == null && b.foreground == null ? Color.lerp(a.color, b.color, t) : null,
    backgroundColor: a.background == null && b.background == null ? Color.lerp(a.backgroundColor, b.backgroundColor, t) : null,
    fontSize: ui.lerpDouble(a.fontSize ?? b.fontSize, b.fontSize ?? a.fontSize, t),
    fontWeight: FontWeight.lerp(a.fontWeight, b.fontWeight, t),
    fontStyle: t < 0.5 ? a.fontStyle : b.fontStyle,
    letterSpacing: ui.lerpDouble(a.letterSpacing ?? b.letterSpacing, b.letterSpacing ?? a.letterSpacing, t),
    wordSpacing: ui.lerpDouble(a.wordSpacing ?? b.wordSpacing, b.wordSpacing ?? a.wordSpacing, t),
    textBaseline: t < 0.5 ? a.textBaseline : b.textBaseline,
    height: ui.lerpDouble(a.height ?? b.height, b.height ?? a.height, t),
    leadingDistribution: t < 0.5 ? a.leadingDistribution : b.leadingDistribution,
    locale: t < 0.5 ? a.locale : b.locale,
    foreground: (a.foreground != null || b.foreground != null)
      ? t < 0.5
        ? a.foreground ?? (Paint()..color = a.color!)
        : b.foreground ?? (Paint()..color = b.color!)
      : null,
    background: (a.background != null || b.background != null)
      ? t < 0.5
        ? a.background ?? (Paint()..color = a.backgroundColor!)
        : b.background ?? (Paint()..color = b.backgroundColor!)
      : null,
    shadows: t < 0.5 ? a.shadows : b.shadows,
    fontFeatures: t < 0.5 ? a.fontFeatures : b.fontFeatures,
    fontVariations: t < 0.5 ? a.fontVariations : b.fontVariations,
    decoration: t < 0.5 ? a.decoration : b.decoration,
    decorationColor: Color.lerp(a.decorationColor, b.decorationColor, t),
    decorationStyle: t < 0.5 ? a.decorationStyle : b.decorationStyle,
    decorationThickness: ui.lerpDouble(a.decorationThickness ?? b.decorationThickness, b.decorationThickness ?? a.decorationThickness, t),
    debugLabel: lerpDebugLabel,
    fontFamily: t < 0.5 ? a._fontFamily : b._fontFamily,
    fontFamilyFallback: t < 0.5 ? a._fontFamilyFallback : b._fontFamilyFallback,
    package: t < 0.5 ? a._package : b._package,
    overflow: t < 0.5 ? a.overflow : b.overflow,
  );
}