How to navigate pageview pages with overwrite back button in AppBar? - flutter

I have a pageview view and it works with sliding. But how do I integrate this back button as leading: Icon(backbutton), when navigating between forms in the pageview? Thanks
screen1.dart
import 'package:app/src/features/examples/components/body.dart';
class OnboardingExampleFlowPage extends StatelessWidget {
static String routeName = "/onboarding_example_flow";
const OnboardingExampleFlowPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 1,
backgroundColor: AppColors.monochromeWhite,
title: Text(context.l10n.buttonBack),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {},
),
),
body: const Body(),
);
}
}
Body has pageview:
body.dart
class _BodyState extends State<Body> {
int currentPage = 0;
final PageController controller = PageController();
#override
void dispose() {
super.dispose();
controller.dispose();
}
#override
Widget build(BuildContext context) {
final List<Widget> formPages = <Widget>[
ExampleContent01(controller: controller),
ExampleContent02(controller: controller),
ExampleContent03(controller: controller),
ExampleContent04(controller: controller),
];
return SafeArea(
child: SizedBox(
child: Column(
children: [
const SizedBox(height: 6),
AppStepper(
currentPage: currentPage,
length: formPages.length,
noSkip: true,
),
Expanded(
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: getProportionateScreenWidth(20),
),
child: PageView(
controller: controller,
onPageChanged: (value) => setState(() => currentPage = value),
children: formPages,
),
),
),
],
),
),
);
}
These forms: There are contents in ExampleScreens, but I did not add their code because there are AppBar and Pageview in the code I added.
here is view: want to be able to go back inside pageview.
Thanks a lot!

Just move the controller up, to the parent widget, so it's possible to navigate the pages with it.
Check out the live demo on DartPad.
The code is going to be like the following:
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const OnboardingExampleFlowPage(),
scrollBehavior: MyCustomScrollBehavior(),
debugShowCheckedModeBanner: false,
);
}
}
class OnboardingExampleFlowPage extends StatefulWidget {
static String routeName = "/onboarding_example_flow";
const OnboardingExampleFlowPage({Key? key}) : super(key: key);
#override
State<OnboardingExampleFlowPage> createState() =>
_OnboardingExampleFlowPageState();
}
class _OnboardingExampleFlowPageState extends State<OnboardingExampleFlowPage> {
final PageController controller = PageController();
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
extendBodyBehindAppBar: true,
appBar: AppBar(
elevation: 1,
title: const Text('Back'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
controller.previousPage(
duration: const Duration(milliseconds: 250),
curve: Curves.easeOut,
);
},
),
),
body: Body(controller: controller),
);
}
}
class Body extends StatefulWidget {
const Body({super.key, required this.controller});
final PageController controller;
#override
State<Body> createState() => _BodyState();
}
class _BodyState extends State<Body> {
int currentPage = 0;
#override
Widget build(BuildContext context) {
const List<Widget> formPages = [
Center(child: Text('Page 1')),
Center(child: Text('Page 2')),
Center(child: Text('Page 3')),
Center(child: Text('Page 4')),
];
return SafeArea(
child: SizedBox(
child: Column(
children: [
const SizedBox(height: 6),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: PageView(
controller: widget.controller,
onPageChanged: (value) => setState(() => currentPage = value),
children: formPages,
),
),
),
],
),
),
);
}
}
// Enables scrolling with mouse dragging
class MyCustomScrollBehavior extends MaterialScrollBehavior {
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}

Dont have body widget in separate file
Put it in the _OnboardingExampleFlowPageState instead.
And it is the _OnboardingExampleFlowPageState that should have controller and
currentIndex variables.
So on leading button click you'll do something like this:
onPressed: () {
if (currentPage > 0) {
controller.previousPage(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
setState(() {
currentPage--;
});
}
},

Related

black screen with Dismissible

i am trying to call Navigator.pop(context); from Dismissible like following
#override
Widget build(BuildContext context) {
return Dismissible(
key: const Key('some key here'),
direction: DismissDirection.down,
onDismissed: (l) {
Navigator.pop(context);
},
child: const Scaffold(
backgroundColor: Colors.yellowAccent,
),
);
}
}
it is work fine but the problem is once i swipe i see black screen !
how could i make it transparent so i can see the previous page instead of black screen ..
if it is not possible with Dismissible please suggest to me any other way to make it done
import 'package:flutter/material.dart';
class myFirstPag extends StatefulWidget {
const myFirstPag({Key? key}) : super(key: key);
#override
myFirstPagState createState() => myFirstPagState();
}
class myFirstPagState extends State<myFirstPag> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return mySecPag();
}
)
);
},
child: const Scaffold(
backgroundColor: Colors.red,
body: Center(child: Text('my First Page')
),
),
);
}
}
class mySecPag extends StatefulWidget {
const mySecPag({Key? key}) : super(key: key);
#override
mySecPagState createState() => mySecPagState();
}
class mySecPagState extends State<mySecPag> {
#override
Widget build(BuildContext context) {
return Dismissible(
key: const Key('some key here'),
direction: DismissDirection.down,
onDismissed: (l) {
Navigator.pop(context);
},
child: const Scaffold(
backgroundColor: Colors.yellowAccent,
body: Center(child: Text('my sec page'),),
),
);
}
}
I don't think you will able to achive it by Dismissible widget:
I have modified your code slightly to this:
import 'package:flutter/material.dart';
class myFirstPag extends StatefulWidget {
const myFirstPag({Key? key}) : super(key: key);
#override
myFirstPagState createState() => myFirstPagState();
}
class myFirstPagState extends State<myFirstPag> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return mySecPag();
}));
},
child: const Scaffold(
backgroundColor: Colors.red,
body: Center(child: Text('my First Page')),
),
);
}
}
class mySecPag extends StatefulWidget {
const mySecPag({Key? key}) : super(key: key);
#override
mySecPagState createState() => mySecPagState();
}
class mySecPagState extends State<mySecPag> {
#override
Widget build(BuildContext context) {
return Dismissible(
key: const Key('some key here'),
direction: DismissDirection.down,
background: Container(color: Colors.red),
onDismissed: (l) {
Navigator.pop(context);
},
child: const Scaffold(
backgroundColor: Colors.yellowAccent,
body: Center(
child: Text('my sec page'),
),
),
);
}
}
I have added background property in Dismisible Widget, now whenever you swipe you can see that color will be shown.
But the style you want to achieve, you can do it with CupertinoRoutes.
Example of your code using CupertinoRoutes routes:
class myFirstPag extends StatefulWidget {
const myFirstPag({Key? key}) : super(key: key);
#override
myFirstPagState createState() => myFirstPagState();
}
class myFirstPagState extends State<myFirstPag> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(mySecPag.route());
},
child: const Scaffold(
backgroundColor: Colors.red,
body: Center(child: Text('my First Page')),
),
);
}
}
class mySecPag extends StatefulWidget {
const mySecPag({Key? key}) : super(key: key);
static Route<dynamic> route() {
return CupertinoPageRoute(
builder: (BuildContext context) {
return mySecPag();
},
);
}
#override
mySecPagState createState() => mySecPagState();
}
class mySecPagState extends State<mySecPag> {
#override
Widget build(BuildContext context) {
return const Scaffold(
backgroundColor: Colors.yellowAccent,
body: Center(
child: Text('my sec page'),
),
);
}
}
Here's the full tutorial for it: Link
i found solution finally ..
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Navigation Basics',
home: FirstRoute(),
));
}
class FirstRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: RaisedButton(
child: Text('Open route'),
onPressed: () {
showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: false,
barrierColor: Colors.black.withOpacity(0.5),
transitionDuration: Duration(milliseconds: 400),
context: context,
pageBuilder: (context, anim1, anim2) {
return SecondRoute();
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position: Tween(begin: Offset(0, 1), end: Offset(0, 0))
.animate(anim1),
child: child,
);
},
);
},
),
),
);
}
}
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Dismissible(
direction: DismissDirection.vertical,
key: const Key('key'),
onDismissed: (_) => Navigator.of(context).pop(),
child: Scaffold(
appBar: AppBar(
title: Text("Second Route"),
),
body: Align(
alignment: Alignment.center,
child: Container(
height: 300,
width: 300,
child: SizedBox.expand(child: FlutterLogo()),
margin: EdgeInsets.only(bottom: 50, left: 12, right: 12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(40),
),
),
),
),
);
}
}
Try This Code:
it is work fine!
import 'package:flutter/material.dart';
class mySecPag extends StatefulWidget {
const mySecPag({Key? key}) : super(key: key);
#override
mySecPagState createState() => mySecPagState();
}
class mySecPagState extends State<mySecPag> {
#override
Widget build(BuildContext context) {
return Dismissible(
background: Container(
color: Colors.red,
),
key: const Key('some key here'),
direction: DismissDirection.down,
onDismissed: (l) {
},
child: const Scaffold(
backgroundColor: Colors.yellowAccent,
body: Center(child: Text('my Contents'),),
),
);
}
}
You can try this code:
First screen
import 'package:flutter/material.dart';
import 'package:surplus_construction/screens/trash/trash_file.dart';
class NewTrash extends StatefulWidget {
const NewTrash({super.key});
#override
State<NewTrash> createState() => _TrashViewState();
}
class _TrashViewState extends State<NewTrash> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.yellowAccent,
body: Center(
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => TrashView()),
);
},
child: Text('Press me')),
),
),
);
}
}
Second screen:
import 'package:flutter/material.dart';
class TrashView extends StatefulWidget {
const TrashView({super.key});
#override
State<TrashView> createState() => _TrashViewState();
}
class _TrashViewState extends State<TrashView> {
#override
Widget build(BuildContext context) {
return Dismissible(
background: Container(
color: Colors.grey,
),
key: const Key('some key here'),
direction: DismissDirection.down,
onDismissed: (l) {
Navigator.pop(context);
},
child: const Scaffold(
backgroundColor: Colors.red,
),
);
}
}

How to go back to previous screen by clicking on bottom navigation bar item in Flutter

I am using this library persistent_bottom_nav_bar to display bottom navigation bar even on navigating to new screen. Now there are two main pages Page1 and Page2, Page1 is using an icon of home where as Page2 is using an icon of search. In Page1 contain a button which navigate to new screen named as NewPage. What i wanted to achieve is if i navigate to NewPage from Page1 and if i decide to goback to previous screen which is Page1 by clicking on homeicon which is at bottom. So how can i click on bottom item and go back to previous screen? Hope you understand my question
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: HomeScaffold(),
);
}
}
class HomeScaffold extends StatefulWidget {
#override
_HomeScaffoldState createState() => _HomeScaffoldState();
}
class _HomeScaffoldState extends State<HomeScaffold> {
late PersistentTabController _controller;
#override
void initState() {
super.initState();
_controller = PersistentTabController(initialIndex: 0);
}
List<Widget> _buildScreens() {
return [
Page1(),
Page2(),
];
}
List<PersistentBottomNavBarItem> _navBarsItems() {
return [
_buildBottomNavBarItem('Page 1', Icons.home),
_buildBottomNavBarItem('Page 2', Icons.search),
];
}
#override
Widget build(BuildContext context) {
return PersistentTabView.custom(
context,
controller: _controller,
screens: _buildScreens(),
confineInSafeArea: true,
itemCount: 2,
handleAndroidBackButtonPress: true,
stateManagement: true,
screenTransitionAnimation: ScreenTransitionAnimation(
animateTabTransition: true,
curve: Curves.ease,
duration: Duration(milliseconds: 200),
),
customWidget: CustomNavBarWidget(
items: _navBarsItems(),
onItemSelected: (index) {
setState(() {
_controller.index = index; // go back to previous screen if i navigate to new screen
});
},
selectedIndex: _controller.index,
),
// ),
);
}
}
class CustomNavBarWidget extends StatelessWidget {
final int? selectedIndex;
final List<PersistentBottomNavBarItem> items;
final ValueChanged<int>? onItemSelected;
CustomNavBarWidget({
Key? key,
this.selectedIndex,
required this.items,
this.onItemSelected,
});
Widget _buildItem(PersistentBottomNavBarItem item, bool isSelected) {
return Container(
alignment: Alignment.center,
height: kBottomNavigationBarHeight,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Flexible(
child: IconTheme(
data: IconThemeData(
size: 26.0,
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary == null
? item.activeColorPrimary
: item.inactiveColorPrimary),
child: isSelected ? item.icon : item.inactiveIcon ?? item.icon,
),
),
Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Material(
type: MaterialType.transparency,
child: FittedBox(
child: Text(
item.title!,
style: TextStyle(
color: isSelected
? (item.activeColorSecondary == null
? item.activeColorPrimary
: item.activeColorSecondary)
: item.inactiveColorPrimary,
fontWeight: FontWeight.w400,
fontSize: 12.0),
)),
),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Container(
width: double.infinity,
height: kBottomNavigationBarHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: items.map((item) {
int index = items.indexOf(item);
return Flexible(
child: GestureDetector(
onTap: () {
this.onItemSelected!(index);
},
child: _buildItem(item, selectedIndex == index),
),
);
}).toList(),
),
),
);
}
}
PersistentBottomNavBarItem _buildBottomNavBarItem(String title, IconData icon) {
return PersistentBottomNavBarItem(
icon: Icon(icon),
title: title,
activeColorPrimary: Colors.indigo,
inactiveColorPrimary: Colors.grey,
);
}
class Page1 extends StatefulWidget {
const Page1({Key? key}) : super(key: key);
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Container(
child: Center(
child: TextButton(
onPressed: () {
Navigator.push(
context, CupertinoPageRoute(builder: (context) => NewPage()));
},
child: Text('Click'),
),
),
),
);
}
}
class Page2 extends StatefulWidget {
const Page2({Key? key}) : super(key: key);
#override
_Page2State createState() => _Page2State();
}
class _Page2State extends State<Page2> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.red,
body: Container(),
);
}
}
class NewPage extends StatefulWidget {
const NewPage({Key? key}) : super(key: key);
#override
_NewPageState createState() => _NewPageState();
}
class _NewPageState extends State<NewPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(),
);
}
}
Have you tried simply using the built-in Navigator.of(context).pop() function as the onPressed callback?

Flutter how to prevent scroll in a specific area in PageView

I have a page with tabbars as header and Pageview for body. The problem that I'm facing is due to the PageView is scrollable and one of the pages requires to do signatures, when I drag to draw on the signature widget, it makes the whole PageView to scroll. Is there a way to stop pageview to scroll while drawing signatures? Like stop gesture from passing to parent widget?
Thanks
My simple sample code:
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
bottom: ColoredTabBar(
tabBarBackgroundColor,
TabBar(
isScrollable: true,
controller: _tabController,
tabs: _tabsInfo.map((EditSafetyPlanTab tabInfo) {
return Tab(
text: tabInfo.label,
);
}).toList()),
),
),
body: PageView.builder(
controller: _pageController,
onPageChanged: (index) {
if (isPageCanChanged) {
onPageChange(index);
}
},
itemCount: _tabsInfo.length,
itemBuilder: (context, index) => buildPage(index, _tabsInfo),
),
);
Update
I had to add "MyHorizontalDragGestureRecognizer" and enable/disable scroll physics to make it work on Android.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class AppScrollBehavior extends MaterialScrollBehavior {
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
scrollBehavior: AppScrollBehavior(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class MyHorizontalDragGestureRecognizer
extends HorizontalDragGestureRecognizer {
#override
void rejectGesture(int pointer) {
acceptGesture(pointer);
}
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Offset offset = Offset.zero;
final PageController controller = PageController();
ScrollPhysics physics = AlwaysScrollableScrollPhysics();
#override
Widget build(BuildContext context) {
return PageView(
physics: physics,
controller: controller,
children: <Widget>[
Center(
child: RawGestureDetector(
gestures: {
MyHorizontalDragGestureRecognizer:
GestureRecognizerFactoryWithHandlers<
MyHorizontalDragGestureRecognizer>(
() => MyHorizontalDragGestureRecognizer(),
(instance) {
instance.onDown = (_) => disableScroll();
instance.onCancel = () => enableScroll();
instance.onEnd = (_) => enableScroll();
instance.onUpdate = (details) {
setState(() {
offset = details.localPosition;
});
};
},
),
},
child: Container(
color: const Color(0xFFCCCCCC),
width: 200,
height: 200,
child: Center(
child: Text(
'x: ${offset.dx.toStringAsFixed(0)}, y: ${offset.dy.toStringAsFixed(0)}',
),
),
),
),
),
const Center(
child: Text('Second Page'),
),
],
);
}
disableScroll() {
setState(() {
physics = NeverScrollableScrollPhysics();
});
}
enableScroll() {
setState(() {
physics = AlwaysScrollableScrollPhysics();
});
}
}
You have to wrap your widget with RawGestureDetector and register a HorizontalDragGestureRecognizer or a VerticalDragGestureRecognizer depending on your scrollDirection.
The GestureRecognizer of the signature widget will win against the recognizer of the PageView in the gesture arena.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class AppScrollBehavior extends MaterialScrollBehavior {
#override
Set<PointerDeviceKind> get dragDevices => {
PointerDeviceKind.touch,
PointerDeviceKind.mouse,
};
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
scrollBehavior: AppScrollBehavior(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({super.key});
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Offset offset = Offset.zero;
final PageController controller = PageController();
#override
Widget build(BuildContext context) {
return PageView(
physics: const AlwaysScrollableScrollPhysics(),
controller: controller,
children: <Widget>[
Center(
child: RawGestureDetector(
gestures: {
HorizontalDragGestureRecognizer:
GestureRecognizerFactoryWithHandlers<
HorizontalDragGestureRecognizer>(
() => HorizontalDragGestureRecognizer(),
(instance) {
instance.onUpdate = (details) {
setState(() {
offset = details.localPosition;
});
};
},
),
},
child: Container(
color: const Color(0xFFCCCCCC),
width: 200,
height: 200,
child: Center(
child: Text(
'x: ${offset.dx.toStringAsFixed(0)}, y: ${offset.dy.toStringAsFixed(0)}',
),
),
),
),
),
const Center(
child: Text('Second Page'),
),
],
);
}
}
You can use behavior property of GestureDetector:
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () { ... },
)

Flutter Setstate called multiple times (GestureDetector & PageView)

basically I have a swiping screen with elements, where user is able to swipe in left or right direction. When the user is swiping, im calling some functions. Im using GestureDetector for gesture recognitions and PageView.Custom for my items. Probably ListView.Custom does also work, but it doesn't fix my issue I have.
I need a PageController, because I have to control the navigation programatically. And I think the PageController maybe is the reason behind my issue that my functions are called multiple times. How to fix it? Does somebody know why setstate is called that often and what to do to prevent it?
Im providing you a fully working example (minified version) with a print on the swiping right actions, where you can see that its beeing called multiple times.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
#override
Widget build(BuildContext context) {
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
// Routes list for tab navigation Android
final List<Widget> _pages = [
ScreenA(),
ScreenB(func: _selectPage),
];
return Scaffold(
appBar: AppBar(),
body: _pages[currentIndex],
bottomNavigationBar: SafeArea(
child: BottomNavigationBar(
onTap: _selectPage,
iconSize: 22,
currentIndex: currentIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.description),
label: 'ScreenA',
),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.ac_unit_outlined),
label: 'ScreenB'),
],
),
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('HOME'),
);
}
}
class ScreenB extends StatefulWidget {
Function func;
ScreenB({Key key, #required this.func})
: super(key: key);
#override
_ScreenBState createState() => _ScreenBState();
}
class _ScreenBState extends State<ScreenB> {
_ScreenBState();
var _controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.access_alarm_sharp),
onPressed: () async {
widget.func(0);
},
),
],
),
body: PageView.custom(
dragStartBehavior: DragStartBehavior.start,
controller: _controller,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
GestureDetector(
onPanUpdate: (details) async {
if (details.delta.dx < 0) {
_controller.nextPage(
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut);
print('function called');
}
},
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: Text('hi')))))),
);
}
}
Thanks in advance!
The problem is that you are using the onPanUpdate method, which is triggered every time a user drags their finger either right or left. You should use the onPanEnd method, which is only triggered when the user's finger is off the screen after dragging either left or right. The function below will work fine.
onPanEnd: (details) async { if (details.velocity.pixelsPerSecond.dx < 0) { _controller.nextPage( duration: Duration(milliseconds: 200), curve: Curves.easeInOut); print('function called'); } }
Please write this function out of widget build function
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
like this
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentIndex = 0;
// Page selector for tab list
void _selectPage(int index) {
print('page index: $index');
setState(() {
currentIndex = index;
});
}
// Routes list for tab navigation Android
final List<Widget> _pages = [
ScreenA(),
ScreenB(func: _selectPage),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: _pages[currentIndex],
bottomNavigationBar: SafeArea(
child: BottomNavigationBar(
onTap: _selectPage,
iconSize: 22,
currentIndex: currentIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.description),
label: 'ScreenA',
),
BottomNavigationBarItem(
backgroundColor: Theme.of(context).primaryColor,
icon: Icon(Icons.ac_unit_outlined),
label: 'ScreenB'),
],
),
),
);
}
}
class ScreenA extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Text('HOME'),
);
}
}
class ScreenB extends StatefulWidget {
Function func;
ScreenB({Key key, #required this.func})
: super(key: key);
#override
_ScreenBState createState() => _ScreenBState();
}
class _ScreenBState extends State<ScreenB> {
_ScreenBState();
var _controller = PageController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(Icons.access_alarm_sharp),
onPressed: () async {
widget.func(0);
},
),
],
),
body: PageView.custom(
dragStartBehavior: DragStartBehavior.start,
controller: _controller,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.horizontal,
childrenDelegate: SliverChildBuilderDelegate((ctx, pageIndex) =>
GestureDetector(
onPanUpdate: (details) async {
if (details.delta.dx < 0) {
_controller.nextPage(
duration: Duration(milliseconds: 200),
curve: Curves.easeInOut);
print('function called');
}
},
child: Center(
child: Container(
width: 200,
height: 200,
color: Colors.red,
child: Text('hi')))))),
);
}
}

Flutter persistent app bar across PageView

Ideally I would like to set up my Flutter app as follows
PageView to swipe left/right between 3 pages and a bottom navigation bar to serve as a label and also help with navigation
Persistent appbar on top with drawer and contextual icons
Page content in between
As can be seen in the image, I have this mostly set up the way I would like in the following manner
main.dart - app entry point, set up appbar, set up pageview with children for new PeoplePage, new TimelinePage, new StatsPage
people_page.dart
timeline_page.dart
stats_page.dart
These three pages just deliver the content to the PageView children as required.
Is this the correct way to achieve this? On the surface it works fine. The issue I am coming across is that on the people page I want to implement a selectable list that changes the appbar title/color as in this example, but the appbar is set up on the main page. Can I access the appbar globally?
I could build a new appbar for each page, but I dont want a new appbar swiping in when switching page. I'd prefer the appbar to look persistent and only have the content swipe in.
Any guidance on the best way to accomplish this would be appreciated.
I put together a quick example of how you might communicate from your screen down to the pages and then also back again. This should solve your problem.
https://gist.github.com/slightfoot/464fc225b9041c2d66ec8ab36fbdb935
import 'package:flutter/material.dart';
void main() => runApp(TestApp());
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primaryColor: Colors.green[900],
scaffoldBackgroundColor: Colors.grey[200],
),
home: MainScreen(),
);
}
}
class AppBarParams {
final Widget title;
final List<Widget> actions;
final Color backgroundColor;
AppBarParams({
this.title,
this.actions,
this.backgroundColor,
});
}
class MainScreen extends StatefulWidget {
final int initialPage;
const MainScreen({
Key key,
this.initialPage = 0,
}) : super(key: key);
#override
MainScreenState createState() => MainScreenState();
static MainScreenState of(BuildContext context) {
return context.ancestorStateOfType(TypeMatcher<MainScreenState>());
}
}
class MainScreenState extends State<MainScreen> {
final List<GlobalKey<MainPageStateMixin>> _pageKeys = [
GlobalKey(),
GlobalKey(),
GlobalKey(),
];
PageController _pageController;
AppBarParams _params;
int _page;
set params(AppBarParams value) {
setState(() => _params = value);
}
#override
void initState() {
super.initState();
_page = widget.initialPage ?? 0;
_pageController = PageController(initialPage: _page);
WidgetsBinding.instance.addPostFrameCallback((_) {
_pageKeys[0].currentState.onPageVisible();
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: _params?.title,
actions: _params?.actions,
backgroundColor: _params?.backgroundColor,
),
body: PageView(
controller: _pageController,
onPageChanged: _onPageChanged,
children: <Widget>[
PeoplePage(key: _pageKeys[0]),
TimelinePage(key: _pageKeys[1]),
StatsPage(key: _pageKeys[2]),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _page,
onTap: _onBottomNavItemPressed,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
title: Text('people'),
icon: Icon(Icons.people),
),
BottomNavigationBarItem(
title: Text('timeline'),
icon: Icon(Icons.history),
),
BottomNavigationBarItem(
title: Text('stats'),
icon: Icon(Icons.pie_chart),
),
],
),
);
}
#override
void reassemble() {
super.reassemble();
_onPageChanged(_page);
}
void _onPageChanged(int page) {
setState(() => _page = page);
_pageKeys[_page].currentState.onPageVisible();
}
void _onBottomNavItemPressed(int index) {
setState(() => _page = index);
_pageController.animateToPage(
index,
duration: Duration(milliseconds: 400),
curve: Curves.fastOutSlowIn,
);
}
}
abstract class MainPageStateMixin<T extends StatefulWidget> extends State<T> {
void onPageVisible();
}
class PeoplePage extends StatefulWidget {
const PeoplePage({Key key}) : super(key: key);
#override
PeoplePageState createState() => PeoplePageState();
}
class PeoplePageState extends State<PeoplePage> with MainPageStateMixin {
final List<Color> _colors = [
Colors.orange,
Colors.purple,
Colors.green,
];
int _personCount = 3;
#override
void onPageVisible() {
MainScreen.of(context).params = AppBarParams(
title: Text('People'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.person_add),
onPressed: () => setState(() => _personCount++),
),
],
backgroundColor: Colors.green,
);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _personCount,
itemBuilder: (BuildContext context, int index) {
return Card(
child: InkWell(
onTap: () => _onTapCard(index),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Material(
type: MaterialType.circle,
color: _colors[index % _colors.length],
child: Container(
width: 48.0,
height: 48.0,
alignment: Alignment.center,
child: Text('$index', style: TextStyle(color: Colors.white)),
),
),
SizedBox(width: 16.0),
Text(
'Item #$index',
style: TextStyle(
color: Colors.grey[600],
fontSize: 18.0,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
);
},
);
}
void _onTapCard(int index) {
Scaffold.of(context).showSnackBar(SnackBar(content: Text('Item #$index')));
}
}
class TimelinePage extends StatefulWidget {
const TimelinePage({Key key}) : super(key: key);
#override
TimelinePageState createState() => TimelinePageState();
}
class TimelinePageState extends State<TimelinePage> with MainPageStateMixin {
#override
void onPageVisible() {
MainScreen.of(context).params = AppBarParams(
title: Text('Timeline'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.alarm_add),
onPressed: () {},
),
],
backgroundColor: Colors.purple,
);
}
#override
Widget build(BuildContext context) {
return Center(
child: Text('Coming soon'),
);
}
}
class StatsPage extends StatefulWidget {
const StatsPage({Key key}) : super(key: key);
#override
StatsPageState createState() => StatsPageState();
}
class StatsPageState extends State<StatsPage> with MainPageStateMixin {
#override
void onPageVisible() {
MainScreen.of(context).params = AppBarParams(
title: Text('Stats'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add_box),
onPressed: () {},
),
],
backgroundColor: Colors.orange,
);
}
#override
Widget build(BuildContext context) {
return Center(
child: Text('Coming soon'),
);
}
}
One way to tackle this would be to have the AppBar title and background color as state variables, and in your PageView set the onPageChanged to a function. This function takes in the page int and based on the page int it sets the state of the title and color to the values that you desire. For the multiselect list you set the title to the variable which keeps the values you have selected, may be keep it as a state variable in the main page and pass it down to the child component. You can use any of the state management strategies and that should probably work fine.
Example of onPageChanged function:
void onPageChanged(int page) {
String _temptitle = "";
Color _tempColor;
switch (page) {
case 0:
_temptitle = "People";
_tempColor = Colors.pink;
break;
case 1:
_temptitle = "Timeline";
_tempColor = Colors.green;
break;
case 2:
_temptitle = "Stats";
_tempColor = Colors.deepPurple;
break;
}
setState(() {
this._page = page;
this._title = _temptitle;
this._appBarColor = _tempColor;
});
}
So for the multiselect case, instead of setting the title to some constant you set the title to the variable which holds the values of the selected options.
Full code is here:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
PageController _pageController;
int _page = 0;
String _title = "MyApp";
Color _appBarColor = Colors.pink;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(_title),
backgroundColor: _appBarColor,
),
body: PageView(
children: <Widget>[
Container(
child: Center(child: Text("People")),
),
Container(
child: Center(child: Text("Timeline")),
),
Container(
child: Center(child: Text("Stats")),
),
],
controller: _pageController,
onPageChanged: onPageChanged,
),
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.people),
title: Text("People"),
),
BottomNavigationBarItem(
icon: Icon(Icons.access_time),
title: Text("Timeline"),
),
BottomNavigationBarItem(
icon: Icon(Icons.pie_chart),
title: Text("Stats"),
),
],
onTap: navigateToPage,
currentIndex: _page,
),
);
}
void navigateToPage(int page) {
_pageController.animateToPage(page,
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
void onPageChanged(int page) {
String _temptitle = "";
Color _tempColor;
switch (page) {
case 0:
_temptitle = "People";
_tempColor = Colors.pink;
break;
case 1:
_temptitle = "Timeline";
_tempColor = Colors.green;
break;
case 2:
_temptitle = "Stats";
_tempColor = Colors.deepPurple;
break;
}
setState(() {
this._page = page;
this._title = _temptitle;
this._appBarColor = _tempColor;
});
}
#override
void initState() {
super.initState();
_pageController = new PageController();
_title = "People";
}
#override
void dispose() {
super.dispose();
_pageController.dispose();
}
}
You can improve this code for your needs. Hope this was helpful in someway. Let me know if there is something I can improve about this answer.