当指针到达边缘时颤动滚动屏幕
Flutter scroll screen when pointer reaches edge
我有一个包含可拖动项的 GridView。当一个项目被拖到屏幕的 top/bottom 时,我想在那个方向滚动 GridView。
目前我将每个可拖动项目包装在一个 Listener 中,如下所示:
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));
}
}
)
有效,但是滚动一点也不流畅,看起来有点滞后。
我也需要它才能在网络上工作。
有人对此有更好的解决方案吗?
我是这样解决的。使用 TickerProviderStateMixin,您可以获得一个每帧调用一次回调的 Ticker,您可以在其中少量调整滚动偏移量以实现平滑滚动。我使用 Stack 将虚拟 DragTargets 添加到控制代码的列表区域的顶部和底部。我在每个边缘使用了两个,以允许不同的滚动速度。如果您想要更细粒度的控制,您可以使用 Listener 来使用光标位置插入速度。
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;
},
);
}
我有一个包含可拖动项的 GridView。当一个项目被拖到屏幕的 top/bottom 时,我想在那个方向滚动 GridView。
目前我将每个可拖动项目包装在一个 Listener 中,如下所示:
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));
}
}
)
有效,但是滚动一点也不流畅,看起来有点滞后。 我也需要它才能在网络上工作。
有人对此有更好的解决方案吗?
我是这样解决的。使用 TickerProviderStateMixin,您可以获得一个每帧调用一次回调的 Ticker,您可以在其中少量调整滚动偏移量以实现平滑滚动。我使用 Stack 将虚拟 DragTargets 添加到控制代码的列表区域的顶部和底部。我在每个边缘使用了两个,以允许不同的滚动速度。如果您想要更细粒度的控制,您可以使用 Listener 来使用光标位置插入速度。
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;
},
);
}