scroll_velocity_notifier

scroll_velocity_notifier is a lightweight Flutter utility that intercepts scroll notifications and computes smooth, real-time scroll velocity (pixels per second).

It is designed for scroll-aware UI, gesture-driven effects, and advanced animations, without imposing layout constraints or architectural opinions.

From the creator of http_mock_adapter, a widely used Flutter testing utility.

✨ Features

  • πŸ“ Calculates scroll velocity in pixels per second
  • πŸ“‰ Uses Exponential Moving Average (EMA) for smooth values
  • 🌊 Optional overscroll velocity support
  • 🧩 Implemented as a ProxyWidget (zero layout impact)
  • πŸ”Œ Works with any ScrollView
  • 🧠 No global state, no forced state management

πŸ“¦ Installation

Add the dependency to your pubspec.yaml:

dependencies:
  scroll_velocity_notifier: ^0.0.1

Then run:

flutter pub get

πŸ“Έ Demo - gif removes frames so it looks junky on the gif

Scroll velocity demo


🧠 How It Works

The widget listens to ScrollNotifications emitted by scrollable widgets and computes velocity using:

  • Scroll position delta (pixels)
  • Time delta (microseconds)
  • EMA smoothing for stability

The widget does not alter layout or scrolling behavior. It acts purely as a transparent observer in the widget tree.


πŸš€ Basic Usage

Wrap any scrollable widget with ScrollVelocityNotifier:

ScrollVelocityNotifier(
  onNotification: (notification, velocity) {
    debugPrint('Velocity: $velocity px/s');
    return false; // allow notification to bubble up
  },
  child: ListView.builder(
    itemCount: 50,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text('Item $index'),
      );
    },
  ),
)

Further reading


πŸ“ Velocity Semantics

  • Positive velocity β†’ scrolling down
  • Negative velocity β†’ scrolling up
  • Zero velocity β†’ stationary or ignored overscroll
  • Smoothed output β†’ ideal for UI reactions and animations

🌊 Overscroll Support

By default, velocity is reported as 0 during overscroll.

To include overscroll velocity (e.g. when using BouncingScrollPhysics):

ScrollVelocityNotifier(
  includeOversScroll: true,
  onNotification: (notification, velocity) {
    debugPrint('Overscroll velocity: $velocity');
    return false;
  },
  child: ListView(
    physics: const BouncingScrollPhysics(),
    children: const [
      SizedBox(height: 2000),
    ],
  ),
)

🎯 Use Case Examples

Hide / Show AppBar Based on Scroll Speed

double appBarOffset = 0;

ScrollVelocityNotifier(
  onNotification: (notification, velocity) {
    if (velocity > 800) {
      appBarOffset = -100;
    } else if (velocity < -800) {
      appBarOffset = 0;
    }
    return false;
  },
  child: CustomScrollView(
    slivers: [
      SliverAppBar(
        floating: true,
        expandedHeight: 100,
      ),
      SliverList(
        delegate: SliverChildBuilderDelegate(
          (context, index) => ListTile(title: Text('Item $index')),
          childCount: 50,
        ),
      ),
    ],
  ),
)

Trigger Animations Based on Scroll Velocity

ScrollVelocityNotifier(
  onNotification: (notification, velocity) {
    if (velocity.abs() > 1200) {
      debugPrint('Fast scroll detected');
    }
    return false;
  },
  child: ListView(
    children: List.generate(
      30,
      (i) => ListTile(title: Text('Row $i')),
    ),
  ),
)

πŸ”Œ StreamController Integration

ScrollVelocityNotifier can optionally emit scroll velocity updates into a user-provided StreamController.

This allows scroll velocity data to be consumed outside the widget tree, for example by:

  • BLoC / Cubit
  • analytics systems
  • animation coordinators
  • logging or debugging tools

Basic Usage

final controller =
    StreamController<ScrollStreamNotification>.broadcast();

@override
void dispose() {
  controller.close();
  super.dispose();
}

ScrollVelocityNotifier(
  controller: controller,
  child: ListView.builder(
    itemCount: 50,
    itemBuilder: (context, index) {
      return ListTile(title: Text('Item $index'));
    },
  ),
);

🧠 Architectural Notes

  • Implemented using ProxyWidget + ProxyElement
  • No rebuilds are triggered
  • No inherited state
  • No frame callbacks
  • Safe for high-frequency scroll updates

This makes it suitable for large dashboards and complex scroll hierarchies.


πŸ§ͺ Testing

The velocity stream can be tested by driving scroll notifications and asserting expected velocity output:

expect(
  velocity.abs(),
  greaterThan(0),
);

πŸ› οΈ When to Use This Package

βœ” Scroll-aware UI βœ” Velocity-driven animations βœ” Gesture-based visibility logic βœ” Overscroll-sensitive effects βœ” Performance-safe scroll observation


πŸ“„ License

MIT License See LICENSE file for details.


πŸ™Œ Contributions

Issues and pull requests are welcome. If you find a bug or have a feature idea, feel free to open an issue.