createSmoothAnimation function

void createSmoothAnimation({
  1. required double startValue,
  2. required double endValue,
  3. required Duration duration,
  4. Curve curve = Curves.easeOutCubic,
  5. required dynamic onUpdate(
    1. double value
    ),
  6. VoidCallback? onComplete,
})

Creates a smooth animation effect without needing a TickerProvider

This function creates a timer-based animation that updates at approximately 60fps. It's used when traditional Flutter animations aren't suitable, such as when animating values across widget boundaries or outside the build context.

Parameters:

  • startValue: The initial value of the animation
  • endValue: The target value to animate toward
  • duration: How long the animation should take to complete
  • curve: The easing curve to use (defaults to easeOutCubic for natural motion)
  • onUpdate: Callback function that receives the animated value on each frame
  • onComplete: Optional callback that executes when animation finishes

Example:

createSmoothAnimation(
  startValue: 200.0,
  endValue: 400.0,
  duration: Duration(milliseconds: 300),
  onUpdate: (value) => _modalSheetHeightNotifier.state = value,
  onComplete: () => setState(() { isExpanded = true; })
);

Implementation

void createSmoothAnimation({
  required double startValue,
  required double endValue,
  required Duration duration,
  Curve curve = Curves.easeOutCubic,
  required Function(double value) onUpdate,
  VoidCallback? onComplete,
}) {
  final startTime = DateTime.now();
  final totalChange = endValue - startValue;

  if (_showDebugPrints) {
    debugPrint(
        '[🎬 ANIM START] start=$startValue | end=$endValue | duration=${duration.inMilliseconds}ms | curve=$curve');
  }

  // Target ~60fps with 16ms intervals for smooth animation
  const frameInterval = Duration(milliseconds: 16);

  // Create a periodic timer for smooth animation
  Timer.periodic(frameInterval, (timer) {
    // Calculate progress based on elapsed time (0.0 to 1.0)
    final elapsedMilliseconds =
        DateTime.now().difference(startTime).inMilliseconds;
    final normalizedProgress =
        (elapsedMilliseconds / duration.inMilliseconds).clamp(0.0, 1.0);

    // Apply easing curve for natural motion feeling
    final easedProgress = curve.transform(normalizedProgress);

    // Calculate current value
    final currentValue = startValue + (totalChange * easedProgress);

    // Log every 5th frame to avoid spam
    if (elapsedMilliseconds % 80 < 20 && _showDebugPrints) {
      debugPrint(
          '[🎬 ANIM] progress=${(normalizedProgress * 100).toStringAsFixed(0)}% | '
          'value=${currentValue.toStringAsFixed(1)} | '
          'elapsed=${elapsedMilliseconds}ms');
    }

    // Call the update function with current value
    onUpdate(currentValue);

    // Stop timer when animation completes
    if (normalizedProgress >= 1.0) {
      timer.cancel();
      if (_showDebugPrints) {
        debugPrint('[🎬 ANIM COMPLETE] final value=$currentValue');
      }
      onComplete?.call();
    }
  });
}