Nesting PageViews in flutter
I was able to replicate the issue on the nested PageView
. It seems that the inner PageView
overrides the detected gestures. This explains why we're unable to navigate to other pages of the outer PageView
, but the BottomNavigationBar
can. More details of this behavior is explained in this thread.
As a workaround, you can use a single PageView and just hide the BottomNavigationBar
on the outer pages. I've modified your code a bit.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
var index = 0;
final PageController pageController = PageController();
final Curve _curve = Curves.ease;
final Duration _duration = Duration(milliseconds: 300);
var isBottomBarVisible = false;
_navigateToPage(value) {
// When BottomNavigationBar button is clicked, navigate to assigned page
switch (value) {
case 0:
value = 1;
break;
case 1:
value = 2;
break;
case 2:
value = 3;
break;
}
pageController.animateToPage(value, duration: _duration, curve: _curve);
setState(() {
index = value;
});
}
// Set BottomNavigationBar indicator only on pages allowed
_getNavBarIndex(index) {
if (index <= 1)
return 0;
else if (index == 2)
return 1;
else if (index >= 3)
return 2;
else
return 0;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PageViewCeption',
home: Scaffold(
body: Container(
child: PageView(
controller: pageController,
onPageChanged: (page) {
setState(() {
// BottomNavigationBar only appears on page 1 to 3
isBottomBarVisible = page > 0 && page < 4;
print('page: $page bottom bar: $isBottomBarVisible');
index = page;
});
},
children: <Widget>[
Container(
color: Colors.red,
),
Container(
color: Colors.orange,
),
Container(
color: Colors.yellow,
),
Container(
color: Colors.green,
),
Container(color: Colors.lightBlue)
],
),
),
bottomNavigationBar: isBottomBarVisible // if true, generate BottomNavigationBar
? new BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (value) => _navigateToPage(value),
currentIndex: _getNavBarIndex(index),
items: [
BottomNavigationBarItem(icon: Icon(Icons.cake), label: '1'),
BottomNavigationBarItem(icon: Icon(Icons.cake), label: '2'),
BottomNavigationBarItem(icon: Icon(Icons.cake), label: '3')
],
)
//else, create an empty container to hide the BottomNavigationBar
: Container(
height: 0,
),
),
);
}
}

Drugo
Passionate about technology. I am an electrical engineer by formation, with a master degree on electronic integrated devices. Currently working as Software Engineer, with focus on backend development in python.
Updated on December 05, 2022Comments
-
Drugo 10 minutes
I want to nest PageViews in flutter, with a PageView inside a Scaffold inside a PageView. In the outer PageView I will have the logo and contact informations, as well as secundary infos. As a child, I will have a scaffold with the inner PageView and a BottomNavigationBar as the main user interaction screen. Here is the code I have so far:
import 'package:flutter/material.dart'; void main() => runApp(new MyApp()); class MyApp extends StatefulWidget{ @override State<StatefulWidget> createState() { return _MyAppState(); } } class _MyAppState extends State<MyApp>{ int index = 0; final PageController pageController = PageController(); final Curve _curve = Curves.ease; final Duration _duration = Duration(milliseconds: 300); _navigateToPage(value){ pageController.animateToPage( value, duration: _duration, curve: _curve ); setState((){ index = value; }); } @override Widget build(BuildContext context) { return MaterialApp( title: 'PageViewCeption', home: PageView( children: <Widget>[ Container( color: Colors.blue, ), Scaffold( body: PageView( controller: pageController, onPageChanged: (page){ setState(() { index = page; }); }, children: <Widget>[ Container( child: Center( child: Text('1', style: TextStyle(color: Colors.white)) ) ), Container( child: Center( child: Text('2', style: TextStyle(color: Colors.white)) ) ), Container( child: Center( child: Text('3', style: TextStyle(color: Colors.white)) ) ), ], ), backgroundColor: Colors.green, bottomNavigationBar: BottomNavigationBar( type: BottomNavigationBarType.fixed, onTap: (value) =>_navigateToPage(value), currentIndex: index, items: [ BottomNavigationBarItem( icon: Icon(Icons.cake), title: Text('1') ), BottomNavigationBarItem( icon: Icon(Icons.cake), title: Text('2') ), BottomNavigationBarItem( icon: Icon(Icons.cake), title: Text('3') ) ], ), ), Container( color: Colors.blue ) ], ), ); } }
Here is the result:
Problem is: When I am in the inner PageView, I can't get away from it to the outer one scrolling left on the first page, or scrolling right on the last page of the inner PageView. The only way to go back to the outer PageView in scrolling (swiping) on the BottomNavigationBar. In the docs of the Scroll Physics Class we find this in the description:
For example, determines how the Scrollable will behave when the user reaches the maximum scroll extent or when the user stops scrolling.
But I haven't been able to come up with a solution yet. Any thoughts?
Update 1
I had progress working with a CustomScrollPhysics class:
class CustomScrollPhysics extends ScrollPhysics{ final PageController _controller; const CustomScrollPhysics(this._controller, {ScrollPhysics parent }) : super(parent: parent); @override CustomScrollPhysics applyTo(ScrollPhysics ancestor) { return CustomScrollPhysics(_controller, parent: buildParent(ancestor)); } @override double applyBoundaryConditions(ScrollMetrics position, double value) { assert(() { if (value == position.pixels) { throw new FlutterError( '$runtimeType.applyBoundaryConditions() was called redundantly.\n' 'The proposed new position, $value, is exactly equal to the current position of the ' 'given ${position.runtimeType}, ${position.pixels}.\n' 'The applyBoundaryConditions method should only be called when the value is ' 'going to actually change the pixels, otherwise it is redundant.\n' 'The physics object in question was:\n' ' $this\n' 'The position object in question was:\n' ' $position\n' ); } return true; }()); if (value < position.pixels && position.pixels <= position.minScrollExtent){ // underscroll _controller.jumpTo(position.viewportDimension + value); return 0.0; } if (position.maxScrollExtent <= position.pixels && position.pixels < value) {// overscroll _controller.jumpTo(position.viewportDimension + (value - position.viewportDimension*2)); return 0.0; } if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge return value - position.minScrollExtent; if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge return value - position.maxScrollExtent; return 0.0; } }
Which is a modification of the ClampingScrollPhysics applyBoundaryConditions. It kinda works but because of the pageSnapping it is really buggy. It happens, because according to the docs:
Any active animation is canceled. If the user is currently scrolling, that action is canceled.
When the action is canceled, the PageView starts to snap back to the Scafold page, if the user stop draggin the screen, and this messes things up. Any ideas on how to avoid the page snapping in this case, or for better implementation for that matter?