Consider a PageView usage where we have one PageView inside another one:
PageView(
children: [
Container(color: Colors.red),
Column(
children: [
Text('Title'),
PageView(
children: [
Container(color: Colors.green),
Container(color: Colors.yellow),
],
),
],
)
],
);
Such architecture will allow us to transition between the outer PageView to the inner PageView. But once we reached the inner PageView we can't get out anymore.
Here's a gif showcasing it:
In such architecture, we can't "merge" both PageViews into one, because the inner PageView doesn't fill the screen
How could we achieve a similar result while allowing to transition from both PageView without getting stuck?
I have a workaround using NotificationListener
by controlling the page view with PageController
. The user actually control the lower PageView and pass the offset to the higher PageView.
final _pageController1 = PageController(); // Controlled
final _pageController2 = PageController(); // Controller
@override
Widget build(BuildContext context) {
var leftOverScroll = 0.0; // total over-scroll offset on left side
var rightOverScroll = 0.0;
return PageView(
physics: const ClampingScrollPhysics(),
controller: _pageController1,
children: [
Container(color: Colors.red),
Column(
children: [
const Text('Title'),
Expanded(
child: NotificationListener(
onNotification: (notification) {
// over scroll to the left side
if (notification is OverscrollNotification &&
notification.overscroll < 0) {
leftOverScroll += notification.overscroll;
_pageController1.position.correctPixels(
_pageController1.position.pixels +
notification.overscroll);
_pageController1.position.notifyListeners();
}
// scroll back after left over scrolling
if (leftOverScroll < 0) {
if (notification is ScrollUpdateNotification) {
final newOverScroll = math.min(
notification.metrics.pixels + leftOverScroll, 0.0);
final diff = newOverScroll - leftOverScroll;
_pageController1.position.correctPixels(
_pageController1.position.pixels + diff);
_pageController1.position.notifyListeners();
leftOverScroll = newOverScroll;
_pageController2.position.correctPixels(0);
_pageController2.position.notifyListeners();
}
}
// release left
if (notification is UserScrollNotification &&
notification.direction == ScrollDirection.idle &&
leftOverScroll != 0) {
_pageController1.previousPage(
curve: Curves.ease,
duration: const Duration(milliseconds: 400));
leftOverScroll = 0;
}
// over scroll to the right side
if (notification is OverscrollNotification &&
notification.overscroll > 0) {
rightOverScroll += notification.overscroll;
_pageController1.position.correctPixels(
_pageController1.position.pixels +
notification.overscroll);
_pageController1.position.notifyListeners();
}
// scroll back after right over scrolling
if (rightOverScroll > 0) {
if (notification is ScrollUpdateNotification) {
final maxScrollExtent =
notification.metrics.maxScrollExtent;
final newOverScroll = math.max(
notification.metrics.pixels +
rightOverScroll -
maxScrollExtent,
0.0);
final diff = newOverScroll - rightOverScroll;
_pageController1.position.correctPixels(
_pageController1.position.pixels + diff);
_pageController1.position.notifyListeners();
rightOverScroll = newOverScroll;
_pageController2.position.correctPixels(maxScrollExtent);
_pageController2.position.notifyListeners();
}
}
// release right
if (notification is UserScrollNotification &&
notification.direction == ScrollDirection.idle &&
rightOverScroll != 0) {
_pageController1.nextPage(
curve: Curves.ease,
duration: const Duration(milliseconds: 400));
rightOverScroll = 0;
}
return false;
},
child: PageView(
physics: ClampingScrollPhysics(),
controller: _pageController2,
children: [
Container(color: Colors.green),
Container(color: Colors.yellow),
],
),
),
),
],
),
Container(color: Colors.blue),
],
);
}
For the part how to avoid the notifications come from the other child widget, I can only think of wrapping it with another NotificationListener
NotificationListener(
onNotification: (_)=> true,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemBuilder: (ctx,index) => Container(width: 50,color: Colors.primaries[index%Colors.primaries.length],),
),
),
At the end:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With