lerp method Null safety

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

Linearly interpolate between two borders.

If a border is null, it is treated as having four BorderSide.none borders.

This supports interpolating between Border and BorderDirectional objects. If both objects are different types but both have sides on one or both of their lateral edges (the two sides that aren't the top and bottom) other than BorderSide.none, then the sides are interpolated by reducing a's lateral edges to BorderSide.none over the first half of the animation, and then bringing b's lateral edges from BorderSide.none over the second half of the animation.

For a more flexible approach, consider ShapeBorder.lerp, which would instead add the two sets of sides and interpolate them simultaneously.

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.

Implementation

static BoxBorder? lerp(BoxBorder? a, BoxBorder? b, double t) {
  assert(t != null);
  if ((a is Border?) && (b is Border?)) {
    return Border.lerp(a, b, t);
  }
  if ((a is BorderDirectional?) && (b is BorderDirectional?)) {
    return BorderDirectional.lerp(a, b, t);
  }
  if (b is Border && a is BorderDirectional) {
    final BoxBorder c = b;
    b = a;
    a = c;
    t = 1.0 - t;
    // fall through to next case
  }
  if (a is Border && b is BorderDirectional) {
    if (b.start == BorderSide.none && b.end == BorderSide.none) {
      // The fact that b is a BorderDirectional really doesn't matter, it turns out.
      return Border(
        top: BorderSide.lerp(a.top, b.top, t),
        right: BorderSide.lerp(a.right, BorderSide.none, t),
        bottom: BorderSide.lerp(a.bottom, b.bottom, t),
        left: BorderSide.lerp(a.left, BorderSide.none, t),
      );
    }
    if (a.left == BorderSide.none && a.right == BorderSide.none) {
      // The fact that a is a Border really doesn't matter, it turns out.
      return BorderDirectional(
        top: BorderSide.lerp(a.top, b.top, t),
        start: BorderSide.lerp(BorderSide.none, b.start, t),
        end: BorderSide.lerp(BorderSide.none, b.end, t),
        bottom: BorderSide.lerp(a.bottom, b.bottom, t),
      );
    }
    // Since we have to swap a visual border for a directional one,
    // we speed up the horizontal sides' transitions and switch from
    // one mode to the other at t=0.5.
    if (t < 0.5) {
      return Border(
        top: BorderSide.lerp(a.top, b.top, t),
        right: BorderSide.lerp(a.right, BorderSide.none, t * 2.0),
        bottom: BorderSide.lerp(a.bottom, b.bottom, t),
        left: BorderSide.lerp(a.left, BorderSide.none, t * 2.0),
      );
    }
    return BorderDirectional(
      top: BorderSide.lerp(a.top, b.top, t),
      start: BorderSide.lerp(BorderSide.none, b.start, (t - 0.5) * 2.0),
      end: BorderSide.lerp(BorderSide.none, b.end, (t - 0.5) * 2.0),
      bottom: BorderSide.lerp(a.bottom, b.bottom, t),
    );
  }
  throw FlutterError.fromParts(<DiagnosticsNode>[
    ErrorSummary('BoxBorder.lerp can only interpolate Border and BorderDirectional classes.'),
    ErrorDescription(
      'BoxBorder.lerp() was called with two objects of type ${a.runtimeType} and ${b.runtimeType}:\n'
      '  $a\n'
      '  $b\n'
      'However, only Border and BorderDirectional classes are supported by this method.',
    ),
    ErrorHint('For a more general interpolation method, consider using ShapeBorder.lerp instead.'),
  ]);
}