handle nested scrolls but not only scroll in Flutter
Sometimes we should handle nested scrolls but not only scroll widget, such as zoomable widget, in such case we can not use NestedScrollView or NotificationListener because zoomable widget may not receive ScrollNotification.
For example, we have such a widget:
class ScrollableWidget extends StatelessWidget {
const ScrollableWidget({super.key});
@override
Widget build(BuildContext context) {
final Controller controller = Get.put(Controller());
return Listener(
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
x.value++;
} else if (e.scrollDelta.dy < 0) {
x.value--;
}
}
},
child: Obx(() => Text('on mouse wheel up/down ${x.value}')),
);
}
}
We build it inside a SingleChildScrollView:
Obviously, it is a bad experience. How could we handle this case? A quick way is to controll the ScrollPhysics of scrollable widget.
First we create a custom ScrollPhysics and override allowUserScrolling:
home: SingleChildScrollView(
physics: CustomScrollPhysics(Get.put(Controller())),
child: Column(
children: [
...List.generate(7, (index) => Text('Item $index')),
ScrollableWidget(),
...List.generate(7, (index) => Text('Item $index')),
],
),
),
class CustomScrollPhysics extends ScrollPhysics {
const CustomScrollPhysics(this.controll, {super.parent});
final Controller controll;
@override
ScrollPhysics applyTo(ScrollPhysics? ancestor) {
return CustomScrollPhysics(controll, parent: buildParent(ancestor));
}
@override
bool get allowUserScrolling => controll.enableScroll;
}
Then we handle the enableScroll
from our widget:
class ScrollableWidget extends StatelessWidget {
const ScrollableWidget({super.key});
@override
Widget build(BuildContext context) {
final Controller controller = Get.put(Controller());
return Listener(
onPointerSignal: (e) {
if (e is PointerScrollEvent) {
if (e.scrollDelta.dy > 0) {
if (x < 10) {
controller.enableScroll = false;
x.value++;
} else {
controller.enableScroll = true;
}
} else if (e.scrollDelta.dy < 0) {
if (x > 0) {
Get.put(Controller()).enableScroll = false;
x.value--;
} else {
controller.enableScroll = true;
}
}
}
},
child: Obx(() => Text('on mouse wheel up/down ${x.value}')),
);
}
}
That’s it!
It is not the only way to handle the case, sometimes you need override the handleEvent method. For example, we extends ZoomPanBehavior when we use syncfusion_flutter_charts
package:
class CustomZoomPanBehavior extends ZoomPanBehavior {
CustomZoomPanBehavior(
// ...
});
@override
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
if (event is PointerScrollEvent) {
if (event.scrollDelta.dy < 0) {
enableScroll = axisController.zoomFactor <= 0.1;
} else {
enableScroll = axisController.zoomFactor >= 1.0;
}
} else {
enableScroll = true;
}
super.handleEvent(event, entry);
}
}