mirror of
https://github.com/hanskokx/scroll_forwarder.git
synced 2026-08-12 06:40:52 +02:00
first commit
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/gestures.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
||||
/// Forwards unclaimed pointer scroll interactions within [child]'s bounds to
|
||||
/// the position resolved by [positionProvider].
|
||||
///
|
||||
/// A scrollable only reacts to mouse wheel input while the pointer hovers
|
||||
/// directly over it, and only reacts to drags that begin on it. Wrapping a
|
||||
/// layout with this widget lets surrounding areas (for example the empty
|
||||
/// margins beside a centered list) also drive the target scrollable, via
|
||||
/// wheel/trackpad scroll signals or touch drags. A nested scrollable always
|
||||
/// claims the interaction first — scroll signals through
|
||||
/// [PointerSignalResolver] and drags through the gesture arena — so the target
|
||||
/// only scrolls when the pointer is not interacting with another scrollable.
|
||||
class ScrollForwarder extends StatefulWidget {
|
||||
const ScrollForwarder({
|
||||
required this.positionProvider,
|
||||
required this.child,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Resolves the scroll position to receive unclaimed scroll interactions.
|
||||
///
|
||||
/// Invoked for every signal or gesture so the current position is always
|
||||
/// used. Return `null` to ignore the interaction.
|
||||
final ScrollPosition? Function() positionProvider;
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
State<ScrollForwarder> createState() => _ScrollForwarderState();
|
||||
}
|
||||
|
||||
class _ScrollForwarderState extends State<ScrollForwarder> {
|
||||
Drag? _drag;
|
||||
Set<PointerDeviceKind> _dragDevices = const <PointerDeviceKind>{};
|
||||
Map<Type, GestureRecognizerFactory> _gestureRecognizers =
|
||||
const <Type, GestureRecognizerFactory>{};
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
final Set<PointerDeviceKind> devices = ScrollConfiguration.of(
|
||||
context,
|
||||
).dragDevices;
|
||||
if (setEquals(devices, _dragDevices)) return;
|
||||
|
||||
_dragDevices = devices;
|
||||
_gestureRecognizers = <Type, GestureRecognizerFactory>{
|
||||
VerticalDragGestureRecognizer:
|
||||
GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
|
||||
() => VerticalDragGestureRecognizer(supportedDevices: devices),
|
||||
(VerticalDragGestureRecognizer instance) {
|
||||
instance
|
||||
..onStart = _onDragStart
|
||||
..onUpdate = _onDragUpdate
|
||||
..onEnd = _onDragEnd
|
||||
..onCancel = _onDragCancel;
|
||||
},
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_drag?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _onPointerSignal(PointerSignalEvent event) {
|
||||
if (event is! PointerScrollEvent || event.scrollDelta.dy == 0.0) return;
|
||||
|
||||
final ScrollPosition? position = widget.positionProvider();
|
||||
if (position == null || !position.hasContentDimensions) return;
|
||||
|
||||
GestureBinding.instance.pointerSignalResolver.register(event, (_) {
|
||||
position.pointerScroll(event.scrollDelta.dy);
|
||||
event.respond(allowPlatformDefault: false);
|
||||
});
|
||||
}
|
||||
|
||||
void _onDragStart(DragStartDetails details) {
|
||||
final ScrollPosition? position = widget.positionProvider();
|
||||
if (position == null) return;
|
||||
_drag = position.drag(details, _disposeDrag);
|
||||
}
|
||||
|
||||
void _disposeDrag() {
|
||||
_drag = null;
|
||||
}
|
||||
|
||||
void _onDragUpdate(DragUpdateDetails details) {
|
||||
_drag?.update(details);
|
||||
}
|
||||
|
||||
void _onDragEnd(DragEndDetails details) {
|
||||
_drag?.end(details);
|
||||
}
|
||||
|
||||
void _onDragCancel() {
|
||||
_drag?.cancel();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Listener(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onPointerSignal: _onPointerSignal,
|
||||
child: RawGestureDetector(
|
||||
behavior: HitTestBehavior.translucent,
|
||||
gestures: _gestureRecognizers,
|
||||
child: widget.child,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user