Make bottomNavigationBar expand down to use whole screen in Flutter - flutter

I am new to Flutter and went on to do the codelabs - first flutter app
Since I'm learning Flutter to develop mobile apps, this tutorials use of NavigationRail isn't too good looking on a phone. I tried to switch it out for a BottomNavigationBar. When changing the background color of the navbar I noticed it doesnt expand to use the full screen. Is it always like this, or is there something making it display it this way in the code?Could'nt find any useful information about this case.
Is it possible to make the green background cover the, here black, area at the bottom of the screen?
Area under bar, white when debugging on real device, here it is black
The final code from the tutorial is poorly adjusted to:
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
class MyHomePage extends StatefulWidget {
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
#override
Widget build(BuildContext context) {
Widget page;
switch(selectedIndex){
case 0:
page = GeneratorPage();
break;
case 1:
page = FavoritesPage();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(
builder: (context, constraints) {
return Scaffold(
body: Center(
child: page,
),
bottomNavigationBar: BottomNavigationBar (
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: 'Favorites',
),
],
currentIndex: selectedIndex,
onTap: _onItemTapped,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
elevation: 0.0,
),
);
}
);
}
void _onItemTapped(int index){
setState(() {
selectedIndex = index;
});
}
}
class FavoritesPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
class GeneratorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
class BigCard extends StatelessWidget {
const BigCard({
Key? key,
required this.pair,
}) : super(key: key);
final WordPair pair;
#override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
elevation: 10,
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase, style: style),
),
);
}
}
Tried changing elevation to 0.0, expandbody and what not. Nothing seems to be working here?

You can use SystemUiOverlayStyle class
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light
.copyWith(systemNavigationBarColor: Colors.greenAccent));
super.initState();
}

Related

pass value between bottomNavigationBar views

How am I supposed to pass a value in this big mess called Flutter?
30 years old php global $var wasn't good?
All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
By the way, I tried using Navigator.push but it seems to open a completely new window, the value is there but I'd need it to show in the tab body not in a new window, below is my code:
main.dart
import 'dart:core';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeView(),
);
}
}
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 40.0,
elevation: 0,
centerTitle: true,
title: Text('Flutter App'),
),
body: tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.red,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white.withOpacity(0.5),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.qr_code),
label: 'Scan',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'List',
),
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
// SECOND TAB WIDGET (custom)
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text(res!),
),
);
}
}
// FIRST TAB WIDGET (qrcode)
class QRViewExample extends StatefulWidget {
const QRViewExample({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _QRViewExampleState();
}
class _QRViewExampleState extends State<QRViewExample> {
Barcode? result;
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
}
controller!.resumeCamera();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(flex: 4, child: _buildQrView(context)),
Expanded(
flex: 1,
child: FittedBox(
fit: BoxFit.contain,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
if (result != null)
Text(
'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}')
else
const Text('Scan a code'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Text('Flash: ${snapshot.data}');
},
)),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(
'Camera facing ${describeEnum(snapshot.data!)}');
} else {
return const Text('loading');
}
},
)),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.pauseCamera();
},
child: const Text('pause',
style: TextStyle(fontSize: 20)),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.resumeCamera();
},
child: const Text('resume',
style: TextStyle(fontSize: 20)),
),
)
],
),
],
),
),
)
],
),
),
),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 150.0
: 300.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.cyanAccent,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
controller.pauseCamera();
setState(() {
result = scanData;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondView(res: result!.code)))
.then((value) => controller.resumeCamera());
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
}
How am I supposed to pass a value in this big mess called Flutter?
With state management tools like InheritedWidget, InheritedModel, Provider, BloC and many more.
30 years old php global $var wasn't good? All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
Well, you shouldn't do that and it's not meant to be done like that. We can use several methods to propagate data down the widget tree. Let me explain this with InheritedWidget. But sometimes you want to go for Provider which is a wrapper class for InheritedWidget.
First we create a class named QRListModel which extends InheritedModel:
class QRListModel extends InheritedWidget {
final List<Barcode> qrList = []; // <- This holds our data
QRListModel({required super.child});
#override
bool updateShouldNotify(QRListModel oldWidget) {
return !listEquals(oldWidget.qrList, qrList);
}
static QRListModel of(BuildContext context) {
final QRListModel? result = context.dependOnInheritedWidgetOfExactType<QRListModel>();
assert(result != null, 'No QRListModel found in context');
return result!;
}
}
updateShouldNotify is a method we have to override to tell Flutter, when we want the widgets to rebuild. We want this to happen when the list changes. The of method is just a handy way to access the QRListModel.
Now wrap a parent widget of both the scan tab view and the list tab view inside QRListModel. We go for HomeView:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: QRListModel(child: HomeView()), // <- here!
);
}
}
We can take any parent widget but it should be a class where we don't call setState. Otherwise our QRListModel also gets rebuilt and our list is gone.
Now we can access QRListModel from anywhere inside the subtree. We need it here:
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
this.controller!.resumeCamera();
});
controller.scannedDataStream.listen((scanData) async {
controller.pauseCamera();
QRListModel.of(context).qrList.add(scanData); // <- Here we access the list
await showDialog(
context: context,
builder: (context) => SimpleDialog(
title: Text("Barcode was added!"),
children: [
Text(scanData.code!)
],
)
);
});
}
And here we read the list:
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: QRListModel.of(context).qrList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(QRListModel.of(context).qrList[index].code ?? "NO"),
),
);
}
);
}
}
Now both pages have access to the qr list. Please do mind that a InheritedWidget can only have final fields. So if you need mutable fields, you need an additional wrapper class. We don't need it as we don't change the list but only its elements.
By the way: You shouldn't call setState inside initState. You did this here:
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {}); // <- Don't call setState inside initState!
super.initState();
}

how to change an image when I click a toggle button(flutter)

enter image description here
I cannot embed image because I don't have 10 reputation.
I want to load image when I click toggle buttons. it seems simple for you but it is hard for me to do it. I am a beginner .
I don't know how to write onPressed button. I already wrote onPressed buttons for toggle button. it seems like it is not possible to write one more oppressed code. I already used the onpressedbutton code to use ToggleButton, but I need one more function to load image but I don't know how to do it
enter code here
import 'package:flutter/material.dart';
import '123.dart';
import '456.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('imageToggle'),),
body: Column(
children: [
Expanded(child: Image.asset('images/meow.jpg')),
Button123(
),
Button456(),
],
),
),
);
}
}
class Button456 extends StatefulWidget {
#override
_Button456State createState() => _Button456State();
}
class _Button456State extends State<Button456> {
List<bool> isSelected = List.generate(3, (index) => false);
#override
Widget build(BuildContext context) {
return Container(
child: ToggleButtons(
isSelected: isSelected,
color: Colors.black,
fillColor: Colors.grey,
children: [
Padding(padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text('cat1'),),
Padding(padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text('cat2'),),
Padding(padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text('cat3'),),
],
onPressed: (int newIndexx) {
setState(() {
for (int index = 0; index < isSelected.length; index++) {
if (index == newIndexx) {
isSelected[index] = true;
} else {
isSelected[index] = false;
}
}
});
},
)
);
}
}
You can make the image url a variable.
String imageLink = 'myImage.png';
class _MyAppState extends State<MyApp> {
onButtonPressed(String value) {
setState(() {imageLink = value});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('imageToggle'),),
body: Column(
children: [
Expanded(child: Image.asset(imageLink)),
TextButton(child: Text('Click Me!'), onPressed: onButtonPressed('newImageLink')),
],
),
),
);
}
}

how Can i make this Single selection Flutter?

I have an Apps which is having a listview with the reaction button in a flutter . I want to make this when a user clicked any of this love icon then it's filled with red color.
enter image description here
enter image description here
Like this image but the problem is when I clicked one of this love icon all of the icons turned into red color but I only want to change the color of love of icon which one is Selected.
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(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool like;
#override
List<String> user = ['Dipto', 'Dipankar', "Sajib", 'Shanto', 'Pranto'];
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView Demu'),
),
body: Center(
child: Container(
child: ListView.builder(
itemCount: user.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(10),
height: 50,
width: MediaQuery.of(context).size.width * 0.8,
color: Colors.yellowAccent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
user[index],
),
Positioned(
child: IconButton(
icon: _iconControl(like),
onPressed: () {
if (like == false) {
setState(() {
like = true;
_iconControl(like);
});
} else {
setState(() {
like = false;
_iconControl(like);
});
}
},
),
),
],
),
);
},
),
)),
);
}
_iconControl(bool like) {
if (like == false) {
return Icon(Icons.favorite_border);
} else {
return Icon(
Icons.favorite,
color: Colors.red,
);
}
}
}
I also try with using parameter but Its failed Like that :
child: IconButton(
icon: _iconControl(true),
onPressed: () {
if (false) {
setState(() {
_iconControl(true);
});
} else {
setState(() {
_iconControl(false);
});
}
},
),
Can you help me Please. Thanks in advance
You can create a modal class to manage the selection of your list
Just create a modal class and add a boolean variable to maintaining selection using. that boolean variable
SAMPLE CODE
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(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool like;
List<Modal> userList = List<Modal>();
#override
void initState() {
userList.add(Modal(name: 'Dipto', isSelected: false));
userList.add(Modal(name: 'Dipankar', isSelected: false));
userList.add(Modal(name: 'Sajib', isSelected: false));
userList.add(Modal(name: 'Shanto', isSelected: false));
userList.add(Modal(name: 'Pranto', isSelected: false));
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('ListView Demu'),
),
body: Center(
child: Container(
child: ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
return Container(
padding: EdgeInsets.all(10),
height: 50,
width: MediaQuery
.of(context)
.size
.width * 0.8,
color: Colors.yellowAccent,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
userList[index].name,
),
Positioned(
child: IconButton(
icon: _iconControl( userList[index].isSelected),
onPressed: () {
setState(() {
userList.forEach((element) {
element.isSelected = false;
});
userList[index].isSelected = true;
});
},
),
),
],
),
);
},
),
)),
);
}
_iconControl(bool like) {
if (like == false) {
return Icon(Icons.favorite_border);
} else {
return Icon(
Icons.favorite,
color: Colors.red,
);
}
}
}
class Modal {
String name;
bool isSelected;
Modal({this.name, this.isSelected = false});
}

Flutter BottomNavigationBar and advanced navigation

I'm building an app with 3 items in the bottom navigation bar. When I change the tab, a different widget is rendering. So far, so good...
import 'package:flutter/material.dart';
class BottomTest extends StatefulWidget {
State createState() => new _BottomTestState();
}
class _BottomTestState extends State<BottomTest> {
List<Widget> _pages;
Widget _selectedContent;
int _bottomIndex;
#override
void initState() {
_bottomIndex = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
_definePages();
return Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Test'),
),
body: _selectedContent ?? _pages[_bottomIndex],
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text("Red")
),
BottomNavigationBarItem(
icon: Icon(Icons.location_on),
title: Text("Blue")
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
title: Text("Green")
)
],
currentIndex: _bottomIndex,
onTap: _onTabTapped,
)
);
}
_definePages() {
_pages = [
Container(
color: Colors.red,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
])
),
Container(color: Colors.blue),
Container(color: Colors.green),
];
}
_defineFloatingActionButton() {
return Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//TODO: How to navigate to another page with still displaying the bottom navigation bar?
}
),
);
}
void _onTabTapped(int index) {
setState(() {
_bottomIndex = index;
_selectedContent = _pages[index];
});
}
}
//POST
class Post extends StatefulWidget {
State createState() => new _PostState();
}
class _PostState extends State<Post> {
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
PostHeader(),
Text('This is a post.')
]);
}
}
//POSTHEADER
class PostHeader extends StatefulWidget {
State createState() => new _PostHeaderState();
}
class _PostHeaderState extends State<PostHeader> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Text('Author'),
onTap: () {
//TODO: This should navigate to another page but still displaying the bottom navigation bar, too.
},
);
}
}
But I can't figure out a best practice for more advance navigation. There are 2 problems that I'm currently facing.
When tabbing the FloatingActionButton on the first page, I want to display a fourth page but the BottomNavigationBar still needs to be visible and operable.
Building a more complex app, I'm dealing with a handful of nested classes. So on my root page, there is a class "Post" and the post contains a class "PostHeader". In PostHeader, there is a ListTile with an onTap callback that should affect my _selectedContent. How do I define this callback? Passing it trough all the different classes didn't seem right.
I thought about defining it in my BottomTest.dart and passing it trough Post and PostTile but that doesn't seem like best practice to me, especially when talking about lots of required callbacks.
Thank you very, very much in advance!
I'm assuming that the fourth page will be shown as any of the other three pages and since the button is in the first page, the fourth page will take the place of the first page and still signal the first bottom "red" field as active.
If that is the case you should create an independent widget for the first page that includes all the logic you need to show other content. Thus you avoid rebuilding the main layout, including the BottomNavigationBar.
You could use something along these lines, by using a FirstPage widget:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new BottomTest(),
);
}
}
class BottomTest extends StatefulWidget {
State createState() => new _BottomTestState();
}
class _BottomTestState extends State<BottomTest> {
List<Widget> _pages;
Widget _selectedContent;
int _bottomIndex;
#override
void initState() {
_bottomIndex = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
_definePages();
return Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Test'),
),
body: _selectedContent ?? _pages[_bottomIndex],
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.add), title: Text("Red")),
BottomNavigationBarItem(
icon: Icon(Icons.location_on), title: Text("Blue")),
BottomNavigationBarItem(
icon: Icon(Icons.people), title: Text("Green"))
],
currentIndex: _bottomIndex,
onTap: _onTabTapped,
));
}
_definePages() {
_pages = [
FirstPage(),
Container(color: Colors.blue),
Container(color: Colors.green),
];
}
void _onTabTapped(int index) {
setState(() {
_bottomIndex = index;
_selectedContent = _pages[index];
});
}
}
//POST
class Post extends StatefulWidget {
State createState() => new _PostState();
}
class _PostState extends State<Post> {
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[PostHeader(), Text('This is a post.')]);
}
}
//POSTHEADER
class PostHeader extends StatefulWidget {
State createState() => new _PostHeaderState();
}
class _PostHeaderState extends State<PostHeader> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Text('Author'),
onTap: () {
//TODO: This should navigate to another page but still displaying the bottom navigation bar, too.
},
);
}
}
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
bool showFirst = true;
_defineFloatingActionButton() {
return Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _onButtonPressed,
),
);
}
_onButtonPressed() {
setState(() {
showFirst = !showFirst;
});
}
_buildFirst() {
return Container(
color: Colors.red,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
]));
}
_buildFourth() {
return Container(
color: Colors.grey,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
]));
}
#override
Widget build(BuildContext context) {
return showFirst ? _buildFirst() : _buildFourth();
}
}
For the second point, perhaps you should open another question so you keep two, more or less, unrelated matters in different answers.

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.