问题描述
正如标题所说,我们想将一个垂直的 ListView 放在一个垂直的 PageView 中并使它们滚动顺利,
As the title says, we wanna put a vertical ListView inside a vertical PageView and make them scrollsmoothly,
我们将实现这样的目标:
We will achieve something like that:
推荐答案
概念:
当用户滚动列表时,如果他们到达底部并再次向同一方向滚动,我们希望页面滚动到下一个而不是列表.反之亦然.
The Concept:
When the user scrolls the list, if they reach its bottom and scroll in the same direction again, we want the page to scroll to the next one not the list. And vice versa.
为了实现这一点,我们将根据用户的触摸手势手动处理两个小部件的滚动.
To achieve that we are gonna handle the scrolling of both widgets manually, depending on the touch gestures of the user.
首先,在父widget的状态下,声明这些字段.
Firstly, in the state of the parent widget, declare these fields.
PageController pageController;
ScrollController activeScrollController;
Drag drag;
//These variables To detect if we are at the
//top or bottom of the list.
bool atTheTop;
bool atTheBottom;
然后初始化并处理它们:
Then initialize and dispose them:
@override
void initState() {
super.initState();
pageController = PageController();
atTheTop = true;
atTheBottom = false;
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
现在让我们创建五种方法来处理用户的垂直拖动.
now let's create five methods for handling the vertical dragging of the user.
void handleDragStart(DragStartDetails details, ScrollController
scrollController) {
if (scrollController.hasClients) {
if (scrollController.position.context.storageContext != null) {
if (scrollController.position.pixels == scrollController.position.minScrollExtent) {
atTheTop = true;
} else if (scrollController.position.pixels == scrollController.position.maxScrollExtent) {
atTheBottom = true;
} else {
atTheTop = false;
atTheBottom = false;
activeScrollController = scrollController;
drag = activeScrollController.position.drag(details, disposeDrag);
return;
}
}
}
activeScrollController = pageController;
drag = pageController.position.drag(details, disposeDrag);
}
void handleDragUpdate(DragUpdateDetails details, ScrollController
scrollController) {
if (details.delta.dy > 0 && atTheTop) {
//Arrow direction is to the bottom.
//Swiping up.
activeScrollController = pageController;
drag?.cancel();
drag = pageController.position.drag(
DragStartDetails(globalPosition: details.globalPosition, localPosition: details.localPosition),
disposeDrag);
} else if (details.delta.dy < 0 && atTheBottom) {
//Arrow direction is to the top.
//Swiping down.
activeScrollController = pageController;
drag?.cancel();
drag = pageController.position.drag(
DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition,
),
disposeDrag);
} else {
if (atTheTop || atTheBottom) {
activeScrollController = scrollController;
drag?.cancel();
drag = scrollController.position.drag(
DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition,
),
disposeDrag);
}
}
drag?.update(details);
}
void handleDragEnd(DragEndDetails details) {
drag?.end(details);
if (atTheTop) {
atTheTop = false;
} else if (atTheBottom) {
atTheBottom = false;
}
}
void handleDragCancel() {
drag?.cancel();
}
void disposeDrag() {
drag = null;
}
最后,让我们构建小部件:
And Finally, let's build the widgets:
页面浏览:
@override
Widget build(BuildContext context) {
return PageView(
controller: pageController,
scrollDirection: Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
children: [
MyListView(
handleDragStart: handleDragStart,
handleDragUpdate: handleDragUpdate,
handleDragEnd: handleDragEnd,
pageStorageKeyValue: '1', //Should be unique for each widget.
),
...
],
);
}
列表视图:
class MyListView extends StatefulWidget {
const MyListView({
Key key,
@required this.handleDragStart,
@required this.handleDragUpdate,
@required this.handleDragEnd,
@required this.pageStorageKeyValue,
}) : assert(handleDragStart != null),
assert(handleDragUpdate != null),
assert(handleDragEnd != null),
assert(pageStorageKeyValue != null),
super(key: key);
final ValuesChanged<DragStartDetails, ScrollController> handleDragStart;
final ValuesChanged<DragUpdateDetails, ScrollController> handleDragUpdate;
final ValueChanged<DragEndDetails> handleDragEnd;
//Notice here, the key to save the position scroll of the list.
final String pageStorageKeyValue;
@override
_MyListViewState createState() => _MyListViewState();
}
class _MyListViewState extends State<MyListView> {
ScrollController scrollController;
@override
void initState() {
super.initState();
scrollController = ScrollController();
}
@override
void dispose() {
scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onVerticalDragStart: (details) {
widget.handleDragStart(details, scrollController);
},
onVerticalDragUpdate: (details) {
widget.handleDragUpdate(details, scrollController);
},
onVerticalDragEnd: widget.handleDragEnd,
child: ListView.separated(
key: PageStorageKey<String>(widget.pageStorageKeyValue),
physics: const NeverScrollableScrollPhysics(),
controller: scrollController,
itemCount: 15,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
separatorBuilder: (context, index) {
return const Divider(
thickness: 3,
);
},
),
);
}
}
typedef
用于注入方法:
typedef ValuesChanged<T, E> = void Function(T value, E valueTwo);
注意事项:
注意ListView中PageStorageKey的使用,这样我们可以在用户回滚到上一页时保存列表的滚动位置.
Notes:
Notice the using of PageStorageKey in the ListView, so that we can save the scroll position of the list if the user scrolls back to the previous page.
如果PageView的每个页面都包含一个ListView,则会抛出一个异常,说明
ScrollController附加到多个滚动视图
.这不是致命的,您可以忽略它,一切都会正常进行.或者如果您有解决方案,我很乐意编辑答案.If each page of the PageView will contain a ListView, an exception will be thrown saying that
ScrollController attached to multiple scroll views
. It's not that fatal, you can ignore it and everything will work fine. Orif you have a solution, I'll gladly edit the answer.更新:为每个
ListView
创建ScrollController
并注入它到handleDragStart
&handleDragUpdate
那么你就不会遇到又是那个例外.Update: create
ScrollController
for eachListView
and inject itto tohandleDragStart
&handleDragUpdate
then you will not encounterthat exception again.我已经更新了上面的代码.
I've updated the code above.
如果你有什么想说的,我在这里回复.谢谢.
If you have anything to say, I'm here to reply.Thanks.
这篇关于如何将ListView放入PageView并垂直滚动它们?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!