Persisting BottomNavigationBar with changing AppBar - flutter

So I'm creating an screen with 5 BottomNavigationBarItem(s) that would ideally persist, while the AppBar changes according to the design. Here's my code:
class ExploreState extends State<ExploreScreen> {
int _selectedIndexForBottomNavigationBar = 0;
void _onItemTappedForBottomNavigationBar(int index) {
setState(() {
_selectedIndexForBottomNavigationBar = index;
});
}
#override
Widget build(BuildContext context) {
return new DefaultTabController(
length: 3,
child: new Scaffold(
appBar: AppBar(
bottom: ColoredTabBar(
color: Colors.white,
tabBar: TabBar(
labelColor: Colors.grey[900],
indicator: UnderlineTabIndicator(
borderSide: BorderSide(color: kPrimaryColor, width: 2.0),
),
tabs: <Widget>[
Tab(text: "Job Posts"),
Tab(
text: "Agencies",
),
Tab(
text: "All Applicants",
),
],
),
),
elevation: 0.7,
centerTitle: true,
title: Text(
"Looking For",
style: TextStyle(fontSize: 18),
),
actions: <Widget>[
Icon(Icons.search),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
),
],
),
body: TabBarView(
children: <Widget>[
Jobs(),
Agencies(),
Applicants(),
],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: <BottomNavigationBarItem>[
_bottomNavigationBarItem(
AssetImage("assets/images/search-job.png"), 'Explore'),
_bottomNavigationBarItem(
AssetImage("assets/images/message.png"), 'Messaging'),
_bottomNavigationBarItem(
AssetImage("assets/images/forums.png"), 'Forums'),
_bottomNavigationBarItem(
AssetImage("assets/images/notifications.png"), 'Notifications'),
_bottomNavigationBarItem(
AssetImage("assets/images/profile.png"), 'Edit Profile'),
],
currentIndex: _selectedIndexForBottomNavigationBar,
selectedItemColor: kPrimaryColor,
onTap: _onItemTappedForBottomNavigationBar,
selectedFontSize: 0,
),
),
);
}
BottomNavigationBarItem _bottomNavigationBarItem(
AssetImage icon, String label) {
return BottomNavigationBarItem(
activeIcon: _navItemIcon(
icon, label, [kPrimaryColor, kPrimaryWelcomeColor], Colors.white),
icon: _navItemIcon(
icon, label, [Colors.white, Colors.white], Color(0xFF4E5969)),
label: "",
);
}
Row _navItemIcon(AssetImage icon, String label, List<Color> backgrondColor,
Color? foregroundColor) {
return Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
colors: backgrondColor,
transform: GradientRotation(1.5),
),
),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
children: [
ImageIcon(
icon,
size: 25,
color: foregroundColor,
),
Text(
label,
style: TextStyle(color: foregroundColor, fontSize: 8),
)
],
),
),
),
),
],
);
}
}
class ColoredTabBar extends Container implements PreferredSizeWidget {
ColoredTabBar({this.color = Colors.white, this.tabBar});
final Color color;
final TabBar? tabBar;
#override
Size get preferredSize => tabBar!.preferredSize;
#override
Widget build(BuildContext context) => Container(
color: color,
child: tabBar,
);
}
Upon Login, they land on the 'Explore' screen but was wondering if this structure is ideal. What I was thinking was a 'common' scaffold for a blank body, 5 BottomNavigationBarItems and a changing AppBar Menu items (for instance, "Messaging" has "Conversations" and "Appointments" AppBar items)
How do we go about properly coding this.
Here is a Sample for the "Messaging" BottomNavigationBarItem.

One possible approach:
Create one Scaffold with your BottomNavigationBar.
Use an IndexedStack (see here) as the body of your Scaffold.
Create the different bodies (Explore, Messaging etc.) and use setState when user clicks on one of your BottomNavigationBarItems and change the current index for IndexedStack.
Also create different AppBar widgets for the different bodies (for example in an array), assign it to the Scaffold based on a state value and in the previously mentioned setState update also its value.
This will work until you can or want to keep all these widgets in the same Stateless Widget class. Chances are good that later you will want to separate them into different classes so that your code is more easy to read. In that case you can use ChangeNotifier, ChangeNotifierProvider and Consumer to communicate state changes between your widgets, as explained here.

Related

How to add a background colour to unselected label for a tabbar in flutter

I'm trying to design a tabbar with this pattern of design
But so I'm not able to make the unselected tab have a background just like this
When I try I end with something like this
Is it posible to use the Defaulttabbarcontroller to make this design or there is a way to design my own custom tab bar
This widget will provide you idea, I am using color.withOpacity, you may use colorFilter or others widget,
class TabBarDemo extends StatefulWidget {
const TabBarDemo({super.key});
#override
State<TabBarDemo> createState() => _TabBarDemoState();
}
class _TabBarDemoState extends State<TabBarDemo>
with SingleTickerProviderStateMixin {
late final TabController controller = TabController(length: 3, vsync: this)
..addListener(() {
setState(() {});
});
#override
Widget build(BuildContext context) {
final textStyle = TextStyle(color: Colors.blue.shade900);
return Scaffold(
backgroundColor: Colors.purple,
appBar: AppBar(
title: const Text('Tabs Demo'),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Row(
children: [
ActionChip(
backgroundColor: Colors.white,
shape: StadiumBorder(),
onPressed: () {
controller.animateTo(0);
},
label: Text(
"All",
style: controller.index == 0
? textStyle
: textStyle.copyWith(
color: textStyle.color!.withOpacity(.3),
),
),
),
SizedBox(width: 10),
ActionChip(
backgroundColor: Colors.white,
shape: StadiumBorder(),
onPressed: () {
controller.animateTo(1);
},
label: Row(
children: [
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Icon(
Icons.ac_unit,
size: 12,
color: controller.index == 1
? Colors.red
: Colors.red.withOpacity(.4),
),
),
Text(
"Income",
style: controller.index == 1
? textStyle
: textStyle.copyWith(
color: textStyle.color!.withOpacity(.3),
),
),
],
),
),
SizedBox(
width: 10,
),
ActionChip(
backgroundColor: Colors.white,
shape: StadiumBorder(),
onPressed: () {
controller.animateTo(2);
},
label: Text(
"Expenses",
style: controller.index == 2
? textStyle
: textStyle.copyWith(
color: textStyle.color!.withOpacity(.3),
),
)),
],
),
Expanded(
child: TabBarView(
controller: controller,
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
),
),
],
),
),
);
}
}
Based on your case and design, I go with Chips and Wrap widgets if I want to build your design. Then I have a Container below them that acts as TabBarView. So, you need to change the child of the Container when the user taps on the Chip.
However, if you want to stick with TabBar then you need to set a theme when you launch the app. This is how I assign my themes, usually.

How to create a tab bar at center of the screen in flutter?

I'm trying to create a tab bar at the center of the screen using flutter while trying it I gave TabBarView in a column and I was stuck in this error. Please resolve this.
I/flutter ( 3983): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter ( 3983): The following assertion was thrown during performResize():
I/flutter ( 3983): Horizontal viewport was given unbounded height.
I/flutter ( 3983): Viewports expand in the cross axis to fill their container and constrain their children to match
I/flutter ( 3983): their extent in the cross axis. In this case, a horizontal viewport was given an unlimited amount of
I/flutter ( 3983): vertical space in which to expand.
The source code is
class profilePage extends StatefulWidget {
#override
profilePageState createState() => profilePageState();
}
class profilePageState extends State<profilePage>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = new TabController(length: 2, vsync: this);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView(
children: [
Container(
child: Column(crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(
height: 40,
),DefaultTabController(
length: 2,
child: Column(children: [TabBar(
unselectedLabelColor: Colors.black,
labelColor: Colors.red,
tabs: <Widget>[
Tab(
icon: Icon(Icons.people),
),
Tab(
icon: Icon(Icons.person),
)
],controller: _tabController,
indicatorSize: TabBarIndicatorSize.tab,
),TabBarView(
children: <Widget>[Text('people'), Text('Person')],
controller: _tabController,
),
]),
),
],
),
),
],
),
);
}
}
You can see the above model of the image what I'm trying to achieve. I've tried many things but I've stuck here.
How to rectify this error and how to create a tab bar at the center of my screen?
I added a demo of what you are trying to get (I followed the Image you posted):
NOTE : I had to make few changes to the way you arranged your widget tree.
class profilePage extends StatefulWidget {
#override
profilePageState createState() => profilePageState();
}
class profilePageState extends State<profilePage> {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
title: Text(
'My Profile',
),
centerTitle: true,
backgroundColor: Colors.grey[700].withOpacity(0.4),
elevation: 0,
// give the app bar rounded corners
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20.0),
bottomRight: Radius.circular(20.0),
),
),
leading: Icon(
Icons.menu,
),
),
body: Column(
children: <Widget>[
// construct the profile details widget here
SizedBox(
height: 180,
child: Center(
child: Text(
'Profile Details Goes here',
),
),
),
// the tab bar with two items
SizedBox(
height: 50,
child: AppBar(
bottom: TabBar(
tabs: [
Tab(
icon: Icon(Icons.directions_bike),
),
Tab(
icon: Icon(
Icons.directions_car,
),
),
],
),
),
),
// create widgets for each tab bar here
Expanded(
child: TabBarView(
children: [
// first tab bar view widget
Container(
color: Colors.red,
child: Center(
child: Text(
'Bike',
),
),
),
// second tab bar viiew widget
Container(
color: Colors.pink,
child: Center(
child: Text(
'Car',
),
),
),
],
),
),
],
),
),
);
}
}
OUTPUT:
To put the TabBar at the center of the screen, your Profile Container's height should be the screen height divided by 2
Like this
class profilePage extends StatefulWidget {
#override
profilePageState createState() => profilePageState();
}
class profilePageState extends State<profilePage>
with SingleTickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
_tabController = new TabController(length: 2, vsync: this);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: MediaQuery.of(context).size.height /2,
child: Center(child: Text("Profile"),),
color: Colors.blue,
),
TabBar(
unselectedLabelColor: Colors.black,
labelColor: Colors.red,
tabs: [
Tab(
icon: Icon(Icons.people),
),
Tab(
icon: Icon(Icons.person),
)
],
controller: _tabController,
indicatorSize: TabBarIndicatorSize.tab,
),
Expanded(
child: TabBarView(
children: [Text('people'), Text('Person')],
controller: _tabController,
),
),
],
),
),
);
}
}
Result:
Also can try this,
AppBar(
leading: IconButton(
constraints: BoxConstraints(),
padding: EdgeInsets.zero,
icon: Container(
padding: EdgeInsets.fromLTRB(10, 5, 0, 5),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(20)),
// color: MyColors.primaryColorLight.withAlpha(20),
color: Colors.white,
),
child: Icon(
Icons.arrow_back_ios,
color: kPrimaryColor,
// size: 16,
),
),
onPressed: () => Navigator.of(context).pop(),
),
backgroundColor: kPrimaryColor,
toolbarHeight: 80,
elevation: 5.0,
title: TabBar(//Add tab bar to title
indicator: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(30)),
color: Colors.white),
labelColor: kPrimaryColor,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.label,
isScrollable: true,
tabs: [
Container(
height: 30.0,
width: 100,
child: Tab(
child: Align(
alignment: Alignment.center,
child: Text(
"Ongoing",
style: TextStyle(fontSize: 16),
),
),
),
),
Container(
width: 100,
height: 30.0,
child: Tab(
child: Align(
alignment: Alignment.center,
child: Text(
"Requests",
style: TextStyle(fontSize: 16),
),
),
),
),
],
),
)

How to get gradient bottom navigation tab in flutter?

There is a package on pub
https://pub.dev/packages/gradient_bottom_navigation_bar
but this is not updated for a very long time.
So, is there a way to create own custom navigation bar with a gradient effect?
something like this...
All it's possible with Flutter, one option could be use a transparent background in your BottomNavigationBar and put it inside a container with a BoxDecoration, try the next:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Text("Hello"),
),
bottomNavigationBar: _createBottomNavigationBar(),
),
);
}
Widget _createBottomNavigationBar() {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF00D0E1), Color(0xFF00B3FA)],
begin: Alignment.topLeft,
end: Alignment.topRight,
stops: [0.0, 0.8],
tileMode: TileMode.clamp,
),
),
child: BottomNavigationBar(
currentIndex: 0,
onTap: (index) {},
showUnselectedLabels: false,
backgroundColor: Colors.transparent,
type: BottomNavigationBarType.fixed,
elevation: 0,
unselectedItemColor: Colors.white,
selectedIconTheme: IconThemeData(color: Colors.white),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text(
"Home",
style: TextStyle(color: Colors.white),
),
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
title: Text(
"Business",
style: TextStyle(color: Colors.white),
),
),
BottomNavigationBarItem(
icon: Icon(Icons.school),
title: Text(
"School",
style: TextStyle(color: Colors.white),
),
),
],
),
);
}
}
I've got a more basic solution. You can check this DartPad.
The end result:
The trick is this:
Set the background color of BottomNavigationBar as transparent.
Wrap it with Stack.
Add a Container as first child to the Stack.
Set Container's decoration to gradient.
Set its height to 60.

TabBar icon color when tab is selected

I'm trying to change color of the tab icon when tab is selected. I know how to change the color of the icon, but I don't know how to make the color change when I select tab.
My code:
child: AppBar(
bottom: TabBar(
tabs: <Tab>[
Tab(
child: new Row(
children: <Widget>[
new Text("Select", textAlign: TextAlign.start),
new SizedBox(
width: 24.0,
),
new Icon(
Icons.arrow_drop_down,
color: Colors.blue.shade400,
),
],
....
)
)
]
)
)
To change the selected tab color, you just need to add this to TabBar :
labelColor: Colors.
unselectedLabelColor: Colors.white,
Full code:
DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
labelColor: Colors.deepOrange,
unselectedLabelColor: Colors.white,
tabs: <Tab>[
Tab(
child: Row(
children: <Widget>[
Text("Select", textAlign: TextAlign.start),
SizedBox(
width: 24.0,
),
Icon(
Icons.arrow_drop_down,
color: Colors.blue.shade400,
),
],
),
),
Tab(
child: Row(
children: <Widget>[
Text("Select", textAlign: TextAlign.start),
SizedBox(
width: 24.0,
),
Icon(
Icons.arrow_drop_down,
color: Colors.blue.shade400,
),
],
),
),
],
),
),
body: Center(
child: Container(
child: Text("This is a page blahblah"),
),
),
),
)
EDIT:
If u want to only change the icon color, then add color to Text and remove the color from the Icon in Tab, Code:
DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
labelColor: Colors.deepOrange,
unselectedLabelColor: Colors.white,
tabs: <Tab>[
Tab(
child: Row(
children: <Widget>[
Text("Select", textAlign: TextAlign.start, style: TextStyle(color: Colors.white),),
SizedBox(
width: 24.0,
),
Icon(
Icons.arrow_drop_down,
),
],
),
),
Tab(
child: Row(
children: <Widget>[
Text("Select", textAlign: TextAlign.start, style: TextStyle(color: Colors.white),),
SizedBox(
width: 24.0,
),
Icon(
Icons.arrow_drop_down,
),
],
),
),
],
),
),
body: Center(
child: Container(
child: Text("This is a page blahblah"),
),
),
),
)
EDIT #2
Now , This code changes the Icon color, but leaves the text color change as default (You can customize the change for color of text like the icon color). Code:
import 'package:flutter/material.dart';
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
onTap: (index){
setState(() {
_currentIndex = index;
});
},
tabs: <Tab>[
Tab(
child: Row(
children: <Widget>[
Text("Select", textAlign: TextAlign.start,),
SizedBox(
width: 24.0,
),
Icon(
Icons.arrow_drop_down,
color: _currentIndex == 0 ? Colors.deepOrange : Colors.white54
),
],
),
),
Tab(
child: Row(
children: <Widget>[
Text("Select", textAlign: TextAlign.start,),
SizedBox(
width: 24.0,
),
Icon(
Icons.arrow_drop_down,
color: _currentIndex == 1 ? Colors.deepOrange : Colors.white54,
),
],
),
),
],
),
),
body: Center(
child: Container(
child: Text("This is a page blahblah"),
),
),
),
);
}
}
TabBar has the properties labelColor and unselectedLabelColor to set a selected/unselected color to any icon and text in the Tabs.
If you want to have a fixed color for icons or texts in the Tabs, you just have to specify that color in the Icon or Text in the Tab to override the color defined in the properties labelColor and unselectedLabelColor of the TabBar.
So, in your case, if you want to have a selected/unselected color for icons and a fixed color for the text, you have to set the labelColor and unselectedLabelColor in the TabBar with the colors for the icons and set a specific color on the text inside the Tabs.
Create a custom tab controller as shown click here
Do something like _tabController.index to get the index of the current tab.
For each tab check if its position(starting from 0) matches the TabController index and display the appropriate icon

Use AppBar Style on a TabBar that is not its child

I want my tab bar to have the same styling as the AppBar.
(using the same icon color, text color and background color)
My layout features a tabbed section to the left and another column to the right. Whithout additional styling the TabBar icons are not visible because both they and the background are white.
So what would be the most convinient way to apply the AppBar style to the TabBar? Thanks for any suggestions!
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> with TickerProviderStateMixin {
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(
vsync: this,
length: 3,
initialIndex: 0,
);
}
#override
Widget build(BuildContext context) {
double bigSize = IconTheme.of(context).size * 4.0;
return MaterialApp(
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Flutter TabBar Style Problem'),
),
body: Row(
children: <Widget>[
Expanded(
child: Column(
children: [
SizedBox(
height: 36.0,
child: TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.cake)),
Tab(icon: Icon(Icons.train)),
Tab(icon: Icon(Icons.info)),
],
),
),
Flexible(
child: TabBarView(
controller: _tabController,
children: [
Icon(Icons.cake, size: bigSize),
Icon(Icons.train, size: bigSize),
Icon(Icons.info, size: bigSize),
],
),
),
],
),
),
Expanded(
child: Container(
color: Colors.amber.shade400,
alignment: FractionalOffset.center,
child: Text('Another Column'),
),
),
],
),
),
);
}
}
When you place a TabBar as the bottom: widget of the AppBar you get a lot of things for free. It gets the background color and a Material widget that has a special feature. It forces the indicator to use white color when the default indicator color is the same as the background color (give a look here).
So when you place a TabBar outside the AppBar you have to handle everything yourself.
But it seems pretty easy wrapping the TabBar in a Material and giving it the primaryColor (the same the framework does here).
So at the end is just replacing your SizedBox by a Material and setting the color. And you get the ink splash for free.
Material(
color: Theme.of(context).primaryColor,
child: TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.cake)),
Tab(icon: Icon(Icons.train)),
Tab(icon: Icon(Icons.info)),
],
),
),
What appBar interally do is inserting a custom DefaultTextStyle that points to Theme.textTheme.title
You can do the exact same thing by wrapping a part of your widget tree into a custom DefaultTextStyle.
DefaultTextStyle(
style: Theme.of(context).textTheme.title,
child: Text('Title')
);
As for the background color, it is availble in another field of Theme.
Container(
color: Theme.of(context).primaryColor,
);
Change Your Code to Something Like This:
class ProductsAdminPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Scaffold is inside DefaultTabController because DefaultTabController needs access to Scaffold
return DefaultTabController(
// Number of tabs
length: 2,
child: Scaffold(
drawer: Drawer(
child: Column(
children: <Widget>[
AppBar(
automaticallyImplyLeading: false,
title: Text('Choose'),
),
ListTile(
title: Text('All Products'),
onTap: () {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (BuildContext context) => ProductsPage()));
},
)
],
),
),
appBar: AppBar(
title: Text('Manage Products'),
// TabBar is added as the bottom widget of the appBar
bottom: TabBar(
tabs: <Widget>[
// No of Tab() Widgets Should be equal to length
Tab(
icon: Icon(Icons.create),
text: 'Create Product',
),
Tab(
icon: Icon(Icons.list),
text: 'My Products',
),
],
),
),
body: TabBarView(
// No of pages should be equal to length
children: <Widget>[ProductCreatePage(), ProductListPage()],
),
),
);
}
}
ThemeData(
appBarTheme: AppBarTheme(
...
),
tabBarTheme: TabBarTheme(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorSize: TabBarIndicatorSize.label,
labelStyle: TextStyle(
fontSize: 20.0,
color: Colors.green,
),
unselectedLabelStyle: TextStyle(
fontSize: 20.0,
color: Colors.green,
),
),
);
We can customise theme in App Level theme as our app bar and then every where in app, tabs will use same style as we define in theme.
I hope it is the easiest approach...