如何将ListView放在PageView中并垂直滚动?
How to Put ListView Inside PageView and Scroll Both of Them Vertically?
正如标题所说,我们想将垂直的 ListView 放在垂直的 PageView 中并使它们滚动
顺利,
我们将实现这样的目标:
概念:
当用户滚动列表时,如果他们到达底部并再次向同一方向滚动,我们希望页面滚动到下一个而不是列表。反之亦然。
为此,我们将根据用户的触摸手势手动处理两个小部件的滚动。
代码:
首先,在父控件的状态下,声明这些字段。
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;
然后初始化并销毁它们:
@override
void initState() {
super.initState();
pageController = PageController();
atTheTop = true;
atTheBottom = false;
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
现在让我们创建五个方法来处理用户的垂直拖动。
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;
}
最后,让我们构建小部件:
浏览量:
@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的使用,这样当用户回滚到上一页时,我们可以保存列表的滚动位置
如果PageView的每一页都包含一个ListView,会抛出异常ScrollController attached to multiple scroll views
。它不是那么致命,您可以忽略它,一切都会正常进行。要么
如果您有解决方案,我很乐意编辑答案。
更新: 为每个 ListView
创建 ScrollController
并注入它
to to to handleDragStart
& handleDragUpdate
那你就不会遇到了
又是那个例外。
我已经更新了上面的代码。
参考文献:
如果你有什么要说的,我在这里回复。
谢谢。
正如标题所说,我们想将垂直的 ListView 放在垂直的 PageView 中并使它们滚动 顺利,
我们将实现这样的目标:
概念:
当用户滚动列表时,如果他们到达底部并再次向同一方向滚动,我们希望页面滚动到下一个而不是列表。反之亦然。
为此,我们将根据用户的触摸手势手动处理两个小部件的滚动。
代码:
首先,在父控件的状态下,声明这些字段。
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;
然后初始化并销毁它们:
@override
void initState() {
super.initState();
pageController = PageController();
atTheTop = true;
atTheBottom = false;
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
现在让我们创建五个方法来处理用户的垂直拖动。
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;
}
最后,让我们构建小部件:
浏览量:
@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的使用,这样当用户回滚到上一页时,我们可以保存列表的滚动位置
如果PageView的每一页都包含一个ListView,会抛出异常
ScrollController attached to multiple scroll views
。它不是那么致命,您可以忽略它,一切都会正常进行。要么 如果您有解决方案,我很乐意编辑答案。更新: 为每个
ListView
创建ScrollController
并注入它 to to tohandleDragStart
&handleDragUpdate
那你就不会遇到了 又是那个例外。我已经更新了上面的代码。
参考文献:
如果你有什么要说的,我在这里回复。 谢谢。