flutter_smooth_wheel_scroll
Smooth, adjustable mouse wheel scrolling for Flutter desktop.
On Flutter desktop a mouse wheel notch jumps the list instantly, and on
Windows it moves about 60 px at once (the system's "lines to scroll", 3 by
default, × 20 px). Flutter has no setting for either: ScrollBehavior and
ScrollPhysics cover dragging and flinging, not wheel input.
This package fixes the two separately.
| Fixes | Applies to | Set up by | |
|---|---|---|---|
SmoothScrollController |
the instant jump | scroll views that use it | the app or a widget package |
SmoothWheelBinding |
the distance per notch | the whole app | the app, once in main() |
Use either one alone or both together.
Getting started
flutter pub add flutter_smooth_wheel_scroll
import 'package:flutter_smooth_wheel_scroll/flutter_smooth_wheel_scroll.dart';
void main() {
SmoothWheelBinding.ensureInitialized(scale: 0.5); // optional: half the distance
runApp(const MyApp());
}
// In a State:
final _controller = SmoothScrollController();
@override
Widget build(BuildContext context) {
return ListView.builder(controller: _controller, ...);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
Smooth scrolling
SmoothScrollController is a drop-in ScrollController. Wheel input animates
instead of jumping; everything else behaves exactly as before.
Choosing a motion
SmoothScrollController(); // spring, 400 ms, no bounce
SmoothScrollController(
motion: const WheelMotion.spring(
duration: Duration(milliseconds: 300),
bounce: 0.1,
),
);
SmoothScrollController(
motion: const WheelMotion.curve(curve: Curves.easeOut),
);
SmoothScrollController(
motion: const WheelMotion.lerp(timeConstant: Duration(milliseconds: 80)),
);
| Motion | Options | Defaults | Feel |
|---|---|---|---|
WheelMotion.spring |
duration, bounce |
400 ms, 0 |
Keeps its speed when you keep scrolling. The smoothest for fast consecutive notches. |
WheelMotion.curve |
duration, curve |
400 ms, Curves.easeOutCubic |
Plays the curve to the target, starting over on every notch. Any Curve works, including your own. |
WheelMotion.lerp |
timeConstant |
60 ms | Fast at first, slowing as it arrives. Speeds up when the target moves further away. |
spring.duration: how long the spring takes to settle; about 99% of the distance is covered by then.spring.bounce:0stops at the target without passing it. Above0passes it and comes back; below0settles more slowly. Same parameters as SwiftUI'sspring(duration:bounce:).lerp.timeConstant: about 63% of the remaining distance is covered per time constant. Smaller is snappier.- A zero duration or time constant turns the animation off.
motion can be changed at any time, for example from a settings screen; the
next wheel input uses it.
_controller.motion = const WheelMotion.spring(duration: Duration(milliseconds: 250));
Behaviour
With every motion:
- Wheel input during a motion adds to the previous target, so fast consecutive notches travel the full distance.
- Scrolling never passes the start or end of the list, even with
bounce, on both clamping and bouncing scroll physics. - Items stay tappable while a motion settles.
userScrollDirectionis reported during the motion, so widgets that hide on scroll keep working.- Dragging, the scrollbar, the keyboard and your own
jumpTo/animateTocalls behave as withScrollController. Wheel input during your ownanimateTostarts from the current position. - In nested scroll views, the outer view takes the wheel once the inner one reaches its end, as in Flutter by default. While the inner motion is still heading for its end, further wheel input already goes to the outer view.
- Only mouse wheels animate. Trackpad scrolling moves as with
ScrollControllerand stops a running motion: on desktop, precision touchpads sendPointerPanZoomevents, which scroll as a drag; on the web, trackpads sendPointerScrollEvents of kindPointerDeviceKind.trackpad, which the controller does not animate.
Wheel distance
SmoothWheelBinding multiplies the distance of every mouse wheel notch,
app-wide, before any scroll view sees it. No scroll view or controller needs
to change.
void main() {
SmoothWheelBinding.ensureInitialized(scale: 0.5);
runApp(const MyApp());
}
-
Call it first in
main(), beforeWidgetsFlutterBinding.ensureInitialized()orrunApp(). Once any binding is installed, this one can no longer be. -
scale1.0(the default) leaves wheel scrolling unchanged. Below1is slower, above1faster. -
Change it at runtime:
SmoothWheelBinding.instance!.wheelScale = 0.6; -
If your app already has its own binding class, mix in
SmoothWheelBindingMixininstead. -
Importing the package installs nothing. A widget package can depend on this one for
SmoothScrollControllerwithout replacing the app's binding.
Only mouse wheels are scaled. Trackpad scrolling is left alone: on desktop,
precision touchpads send PointerPanZoom events, and on the web, trackpads
send PointerScrollEvents of kind PointerDeviceKind.trackpad. Handlers that
read the size of PointerScrollEvent.scrollDelta see the scaled value;
handlers that read only its sign, such as Ctrl+wheel zoom steps, behave as
before.
Limitations
NestedScrollViewroutes wheel input through its own coordinator and is not smoothed.- A scrollbar with its own controller is not smoothed when the wheel is used over the scrollbar itself.
- A widget between two nested scroll views that claims wheel events through
GestureBinding.instance.pointerSignalResolverdoes not receive the input the inner view passes outward during a motion; the outer view takes it. - In Firefox, Flutter web cannot tell a trackpad from a mouse wheel and reports
both as a mouse. There, trackpad scrolling animates and is scaled by
wheelScale. - A touchpad that the system reports as a mouse wheel rather than as a precision touchpad, on Windows or Linux, is treated as a mouse wheel.
- Developed and checked on Windows. Other desktop platforms use the same Flutter code path but have not been tried by hand. The example has been run in Chrome; web trackpad input has not been tried with a real trackpad.
Example
example/ puts a default list next to a smooth one. Every option
is adjustable with an explanation, and the code for the current settings is
shown ready to copy.
cd example
flutter run -d windows # or: -d chrome
License
MIT
Libraries
- flutter_smooth_wheel_scroll
- Smooth and scalable mouse wheel scrolling for Flutter desktop.