Reputation: 3904
Looking for help on this one, it seemed simple to at first but couldn't figure it out...
Basically I'm trying to make a page with a vertical scrollable listView inside a vertical scrollable pageView. For example the list is on page 2, the user is on page one and scrolls down to page 2. Now he scrolls down until the end of the list and I want his to be able to scroll a bit more that it would go to page 3.
Any suggestions on how to implement this?
Upvotes: 1
Views: 1661
Reputation: 11
This GitHub issue has a handy solution that I think has the "hover" behavior between each page that you need. Here is the sample code provided within the thread:
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: Example()
)
);
}
}
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
PageController _pageController;
ScrollController _listScrollController;
ScrollController _activeScrollController;
Drag _drag;
@override
void initState() {
super.initState();
_pageController = PageController();
_listScrollController = ScrollController();
}
@override
void dispose() {
_pageController.dispose();
_listScrollController.dispose();
super.dispose();
}
void _handleDragStart(DragStartDetails details) {
if (_listScrollController.hasClients && _listScrollController.position.context.storageContext != null) {
final RenderBox renderBox = _listScrollController.position.context.storageContext.findRenderObject();
if (renderBox.paintBounds.shift(renderBox.localToGlobal(Offset.zero)).contains(details.globalPosition)) {
_activeScrollController = _listScrollController;
_drag = _activeScrollController.position.drag(details, _disposeDrag);
return;
}
}
_activeScrollController = _pageController;
_drag = _pageController.position.drag(details, _disposeDrag);
}
void _handleDragUpdate(DragUpdateDetails details) {
if (_activeScrollController == _listScrollController && details.primaryDelta < 0 && _activeScrollController.position.pixels == _activeScrollController.position.maxScrollExtent) {
_activeScrollController = _pageController;
_drag?.cancel();
_drag = _pageController.position.drag(
DragStartDetails(
globalPosition: details.globalPosition,
localPosition: details.localPosition
),
_disposeDrag
);
}
_drag?.update(details);
}
void _handleDragEnd(DragEndDetails details) {
_drag?.end(details);
}
void _handleDragCancel() {
_drag?.cancel();
}
void _disposeDrag() {
_drag = null;
}
@override
Widget build(BuildContext context) {
return RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
VerticalDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(),
(VerticalDragGestureRecognizer instance) {
instance
..onStart = _handleDragStart
..onUpdate = _handleDragUpdate
..onEnd = _handleDragEnd
..onCancel = _handleDragCancel;
}
)
},
behavior: HitTestBehavior.opaque,
child: PageView(
controller: _pageController,
scrollDirection: Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
children: [
ListView(
controller: _listScrollController,
physics: const NeverScrollableScrollPhysics(),
children: List.generate(
25,
(int index) {
return ListTile(
title: Text('Item $index')
);
}
)
),
Center(
child: Text('Page 2')
)
],
)
);
}
}
Upvotes: 1
Reputation: 2617
What works for me is that we need to attach the ListView's scroll controller to listen to the scroll position. The page controller will then move to either next or previous page.
PageController pageController;
ScrollController scrollController;
@override
void initState() {
super.initState();
pageController = PageController();
scrollController = ScrollController();
scrollController.addListener(() {
// listView reaches the bottom
if (scrollController.offset >= scrollController.position.maxScrollExtent - 100 &&
!scrollController.position.outOfRange) {
pageController.nextPage(duration: const Duration(seconds: 1), curve: ElasticOutCurve());
}
// listView reaches the top
if (scrollController.offset <= scrollController.position.minScrollExtent &&
!scrollController.position.outOfRange) {
pageController.previousPage(duration: const Duration(seconds:1), curve: ElasticOutCurve());
}
});
}
Don't forget to attach the controllers
PageView(
controller: pageController,
...
ListView(
controller: scrollController,
...
Upvotes: 4