Flutter scroll screen when pointer reaches edge
Here's how I'm solving it. Using TickerProviderStateMixin, you can obtain a Ticker that invokes a callback once per frame, where you can adjust the scroll offset by a small amount for a smooth scroll. I used a Stack to add dummy DragTargets to the top and bottom of the list area which control the tickers. I used two per edge, to allow different scrolling speeds. You could probably use a Listener to interpolate the speed using the cursor position if you want finer-grained control.
https://www.dartpad.dev/acb83fdbbbbb0fd765cd5afa414a8942
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Stack(
children: [
ListView.separated(
controller: controller,
itemCount: 50,
itemBuilder: (context, index) {
return buildLongPressDraggable(index);
},
separatorBuilder: (context, index) {
return Divider();
},
),
Positioned(
top: 0, left: 0, right: 0, height: 25, child: buildEdgeScroller(-10)),
Positioned(
top: 25, left: 0, right: 0, height: 25, child: buildEdgeScroller(-5)),
Positioned(
bottom: 25, left: 0, right: 0, height: 25, child: buildEdgeScroller(5)),
Positioned(
bottom: 0, left: 0, right: 0, height: 25, child: buildEdgeScroller(10)),
],
),
);
}
Widget buildEdgeScroller(double offsetPerFrame) {
return DragTarget<int>(
builder: (context, candidateData, rejectedData) => Container(),
onWillAccept: (data) {
scrollTicker = this.createTicker((elapsed) {
if (!controller.hasClients) {
return;
}
final position = controller.position;
if ((offsetPerFrame < 0 && position.pixels <= position.minScrollExtent) ||
(offsetPerFrame > 0 && position.pixels >= position.maxScrollExtent)) {
scrollTicker.stop();
scrollTicker.dispose();
scrollTicker = null;
} else {
controller.jumpTo(controller.offset + offsetPerFrame);
}
});
scrollTicker.start();
return false;
},
onLeave: (data) {
scrollTicker?.stop();
scrollTicker?.dispose();
scrollTicker = null;
},
);
}
SeriousMonk
Updated on December 29, 2022Comments
-
SeriousMonk over 1 year
I have a GridView that contains draggable items. When an item is dragged to the top/bottom of the screen I want to scroll the GridView in that direction.
Currently I wrapped each draggable item in a Listener like so:
Listener( child: _wrap(widget.children[i], i), onPointerMove: (PointerMoveEvent event) { if (event.position.dy >= MediaQuery.of(context).size.height - 100) { // 120 is height of your draggable. widget.scrollController.animateTo( widget.scrollController.offset + 120, curve: Curves.easeOut, duration: const Duration(milliseconds: 200)); }if (event.position.dy <= kToolbarHeight + MediaQueryData.fromWindow(window).padding.top + 100) { // 120 is height of your draggable. widget.scrollController.animateTo( widget.scrollController.offset - 120, curve: Curves.easeOut, duration: const Duration(milliseconds: 200)); } } )
It works, but the scroll is not smooth at all and looks kind of laggy. I would need it to work on web too.
Does anyone have a better solution for this?
-
Abhilash Chandran about 3 yearsCould you share a demo in dartpad. Would be helpful to check the problem directly. One thing I am aware is often the position returned in case of web turnsout to be zero.
-
-
SeriousMonk about 3 yearsThank you! I thought about using drag targets for this but didn't know how. I'm currently migrating my app to null-safety, but as soon as I'm done with that I'll try you solution. I am very confident it'll work just fine :)
-
SeriousMonk about 3 yearsWorks like a charm! For people using null-safety just use Ticker? scrollTicker insted of Ticker scrollTicker and inside the buildEdgeScroller method use scrollTicker!.stop(), scrollTicker!.dispose() and scrollTicker?.start()
-
Chuender about 2 yearsYou rock, @blake raymond! Thanks!