[英]How to Put ListView Inside PageView and Scroll Both of Them Vertically?
當用戶滾動列表時,如果他們到達底部並再次向同一方向滾動,我們希望頁面滾動到下一個而不是列表。 反之亦然。
為了實現這一點,我們將根據用戶的觸摸手勢手動處理兩個小部件的滾動。
首先,在父widget的state中,聲明這些字段。
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的使用,這樣如果用戶回滾到上一頁,我們就可以保存列表的滾動position。
如果 PageView 的每個頁面都包含一個 ListView,則會拋出一個異常,說ScrollController attached to multiple scroll views
。 它不是那么致命,您可以忽略它,一切都會正常工作。 或者,如果您有解決方案,我很樂意編輯答案。
更新:為每個ListView
創建ScrollController
並將其注入到handleDragStart
和handleDragUpdate
然后您將不會再次遇到該異常。
我已經更新了上面的代碼。
如果你有什么要說的,我在這里回復。 謝謝。
當滾動視圖太小而無法容納整個屏幕時,我修改了 Tayan 對訴訟案件的回應。 我還添加了水平方向支持,並將拖動處理邏輯移至小部件本身。 還支持任何類型的滾動視圖(不僅是 ListView)這是代碼:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
///This widget provides correct scrolling and swiping behavior when scrolling view are placed inside pageview with same direction
///The widget works both for vertical and horizontal scrolling direction
///To use this widget you have to do following:
///* set physics: NeverScrollableScrollPhysics(parent: ClampingScrollPhysics()) argument for both PageView and ScrollView
///* create scrollController for ScrollView and pageController for PageView. Do not forget to dispose then at dispose() State callback
///* make sure that scrolling direction on both views are the same and equals to scrollDirection argument here
class PageViewScrollableChild extends StatefulWidget {
final Widget child;
final ScrollController scrollController;
final PageController pageController;
final Axis scrollDirection;
const PageViewScrollableChild(
{Key? key,
required this.scrollController,
required this.pageController,
required this.child,
required this.scrollDirection})
: super(key: key);
@override
State<StatefulWidget> createState() {
return _PageViewScrollableChildState();
}
}
class _PageViewScrollableChildState extends State<PageViewScrollableChild> {
late bool atTheStart;
late bool atTheEnd;
///true if scroll view content does not overscroll screen size
late bool bothSides;
ScrollController? activeScrollController;
Drag? drag;
@override
void initState() {
super.initState();
atTheStart = true;
atTheEnd = false;
bothSides = false;
}
void handleDragStart(DragStartDetails details, ScrollController scrollController) {
if (scrollController.hasClients) {
if (scrollController.position.minScrollExtent == 0 && scrollController.position.maxScrollExtent == 0) {
bothSides = true;
} else if (scrollController.position.pixels <= scrollController.position.minScrollExtent) {
atTheStart = true;
} else if (scrollController.position.pixels >= scrollController.position.maxScrollExtent) {
atTheEnd = true;
} else {
atTheStart = false;
atTheEnd = false;
activeScrollController = scrollController;
drag = activeScrollController?.position.drag(details, disposeDrag);
return;
}
}
activeScrollController = widget.pageController;
drag = widget.pageController.position.drag(details, disposeDrag);
}
void handleDragUpdate(DragUpdateDetails details, ScrollController scrollController) {
final offset = widget.scrollDirection == Axis.vertical ? details.delta.dy : details.delta.dx;
if (offset > 0 && (atTheStart || bothSides)) {
//Arrow direction is to the bottom.
//Swiping up.
activeScrollController = widget.pageController;
drag?.cancel();
drag = widget.pageController.position.drag(
DragStartDetails(globalPosition: details.globalPosition, localPosition: details.localPosition), disposeDrag);
} else if (offset < 0 && (atTheEnd || bothSides)) {
//Arrow direction is to the top.
//Swiping down.
activeScrollController = widget.pageController;
drag?.cancel();
drag = widget.pageController.position.drag(
DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition,
),
disposeDrag);
} else if (atTheStart || atTheEnd) {
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 (atTheStart) {
atTheStart = false;
} else if (atTheEnd) {
atTheEnd = false;
}
}
void handleDragCancel() {
drag?.cancel();
}
void disposeDrag() {
drag = null;
}
@override
Widget build(BuildContext context) {
final scrollDirection = widget.scrollDirection;
return GestureDetector(
onVerticalDragStart:
scrollDirection == Axis.vertical ? (details) => handleDragStart(details, widget.scrollController) : null,
onVerticalDragUpdate:
scrollDirection == Axis.vertical ? (details) => handleDragUpdate(details, widget.scrollController) : null,
onVerticalDragEnd: scrollDirection == Axis.vertical ? (details) => handleDragEnd(details) : null,
onHorizontalDragStart:
scrollDirection == Axis.horizontal ? (details) => handleDragStart(details, widget.scrollController) : null,
onHorizontalDragUpdate:
scrollDirection == Axis.horizontal ? (details) => handleDragUpdate(details, widget.scrollController) : null,
onHorizontalDragEnd: scrollDirection == Axis.horizontal ? (details) => handleDragEnd(details) : null,
child: widget.child,
);
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.