Available on pub.dev
When we work with scrolling in Flutter, we usually think in terms of position.
Where is the user right now?
How many pixels did they scroll?
Are we at the top or the bottom?
But scroll has another dimension that often gets ignored: speed.
How fast the user scrolls carries intent.
It tells you how something is happening, not just where.
Why scroll velocity matters
Think about these interactions:
- An AppBar hides only when the user scrolls fast
- A floating button fades out on quick flicks, but stays on slow scrolls
- Animations trigger based on gesture intent, not offset
- Overscroll feels elastic and alive instead of binary
All of these depend on scroll velocity, not scroll position.
Flutter exposes ScrollNotifications, but it doesn’t give you velocity in a form that is:
- smooth
- stable
- reusable
- decoupled from layout
That’s the gap this package tries to fill.
What scroll_velocity_notifier is
scroll_velocity_notifier is a small Flutter package that calculates real-time scroll velocity (in pixels per second) directly from ScrollNotifications.
It doesn’t replace scroll views.
It doesn’t change layouts.
It doesn’t force any architecture.
It simply observes scrolling and tells you:
“The user is scrolling this fast right now.”
Design goals
From the beginning, the goals were intentionally modest:
No layout impact
The widget should not affect rendering or rebuild children.No global state
Everything should be opt-in and local.No architectural opinion
Works equally well with Bloc, Riverpod, streams, or plain callbacks.Human-friendly values
Raw velocity is noisy — the output should be usable directly.
How it works (conceptually)
The widget listens to ScrollUpdateNotifications and measures:
- the change in scroll position (pixels)
- the time between updates (microseconds)
From that, it calculates velocity in pixels per second.
To make the values stable enough for UI logic, it applies Exponential Moving Average (EMA) smoothing.
This removes jitter and produces velocity that feels intentional rather than mechanical.
Overscroll is optional — and intentional
In many Flutter apps, overscroll is not just an edge case — it’s part of the interaction.
When using physics like BouncingScrollPhysics, the scroll position can move beyond its bounds.
Sometimes you want velocity there. Sometimes you don’t.
For that reason, overscroll handling is explicit.
By default, velocity during overscroll is reported as 0.
You can opt into overscroll velocity when you need it:
ScrollVelocityProvider(
includeOversScroll: true,
onNotification: (notification, velocity) {
debugPrint('Velocity: $velocity px/s');
return false;
},
child: ListView(
physics: const BouncingScrollPhysics(),
children: const [],
),
);
This keeps behavior predictable and avoids accidental effects.
Basic usage with a callback
The simplest way to use the package is via a callback:
ScrollVelocityProvider(
onNotification: (notification, velocity) {
debugPrint('Velocity: $velocity px/s');
return false;
},
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
),
);
You wrap any scrollable widget and start receiving velocity values.
No controllers required.
No rebuilds triggered.
No extra layout widgets.
Using a StreamController (advanced usage)
For more complex scenarios, you can pass a StreamController to receive scroll velocity events.
This is useful when:
- multiple consumers need the same scroll signal
- velocity is handled outside the widget tree
- you want to plug it into Bloc, Cubit, or analytics logic
final controller =
StreamController<ScrollStreamNotification>.broadcast();
@override
void dispose() {
controller.close();
super.dispose();
}
ScrollVelocityProvider(
controller: controller,
child: ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(title: Text('Item $index'));
},
),
);
Listening to the stream:
controller.stream.listen((event) {
debugPrint('Velocity: ${event.velocity}');
});
The widget never owns the controller.
If you pass one, you control its lifecycle.
Why a ProxyWidget?
Internally, the package is implemented using a ProxyWidget and ProxyElement.
This means:
- children are not rebuilt
- layout is untouched
- the widget simply participates in the notification chain
It’s safe to use even in large or deeply nested scroll hierarchies.
What it is not
This package is intentionally small.
It does not:
- animate anything for you
- modify scroll physics
- enforce UX decisions
- hide behavior behind magic thresholds
It gives you data — you decide how to use it.
Final thoughts
Scrolling is one of the most frequent interactions in mobile apps, yet it’s often treated as a binary signal: moved or not moved.
Velocity adds nuance.
It captures intent, rhythm, and subtlety that position alone can’t express.
scroll_velocity_notifier is a small attempt to make that nuance accessible in Flutter — without adding complexity where it doesn’t belong.
📦 Package: scroll_velocity_notifier
🔗 Available on: pub.dev
🛠 Platform: Flutter / Dart
Top comments (0)