In flutter implementing a tab layout is easy and straightforward. This is a simple example from the official documentation:
import 'package:flutter/material.dart';
void main() {
runApp(new TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new DefaultTabController(
length: 3,
child: new Scaffold(
appBar: new AppBar(
bottom: new TabBar(
tabs: [
new Tab(icon: new Icon(Icons.directions_car)),
new Tab(icon: new Icon(Icons.directions_transit)),
new Tab(icon: new Icon(Icons.directions_bike)),
],
),
title: new Text('Tabs Demo'),
),
body: new TabBarView(
children: [
new Icon(Icons.directions_car),
new Icon(Icons.directions_transit),
new Icon(Icons.directions_bike),
],
),
),
),
);
}
}
But here is the thing, I want to get the active tab index so I can apply some logic on certain tabs. I search the documentation but I wasn't able to figure it out. Can you guys help and thanks?
The whole point of DefaultTabController is for it to manage tabs by itself.
If you want some custom tab management, use TabController instead.
With TabController you have access to much more informations, including the current index.
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({Key key}) : super(key: key);
#override
_MyTabbedPageState createState() => new _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage>
with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
new Tab(text: 'LEFT'),
new Tab(text: 'RIGHT'),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = new TabController(vsync: this, length: myTabs.length);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
bottom: new TabBar(
controller: _tabController,
tabs: myTabs,
),
),
body: new TabBarView(
controller: _tabController,
children: myTabs.map((Tab tab) {
return new Center(child: new Text(tab.text));
}).toList(),
),
);
}
}
In this case, using StatefulWidget and State isn't a good idea.
You can get current index by DefaultTabController.of(context).index;.
Follow the code:
...
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(~), Tab(~)
]
),
actions: [
// At here you have to get `context` from Builder.
// If you are not sure about this, check InheritedWidget document.
Builder(builder: (context){
final index = DefaultTabController.of(context).index;
// use index at here...
})
]
)
You can access the current index when the tab is selected by onTap event of TabBar.
TabBar(
onTap: (index) {
//your currently selected index
},
tabs: [
Tab1(),
Tab2(),
]);
Just apply a listener on the TabController.
// within your initState() method
_tabController.addListener(_setActiveTabIndex);
void _setActiveTabIndex() {
_activeTabIndex = _tabController.index;
}
Use DefaultTabController you can get current index easily whether the user changes tabs by swiping or tap on the tab bar.
Important: You must wrap your Scaffold inside of a Builder and you can then retrieve the tab index with DefaultTabController.of(context).index inside Scaffold.
Example:
DefaultTabController(
length: 3,
child: Builder(builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
bottom: TabBar(
isScrollable: true,
tabs: [Text('0'), Text('1'), Text('2')]),
),
body: _buildBody(),
floatingActionButton: FloatingActionButton(
onPressed: () {
print(
'Current Index: ${DefaultTabController.of(context).index}');
},
),
);
}),
),
New working solution
I'd suggest you to use TabController for more customisations. To get active tab index you should use _tabController.addListener and _tabController.indexIsChanging.
Use this full code snippet:
class CustomTabs extends StatefulWidget {
final Function onItemPressed;
CustomTabs({
Key key,
this.onItemPressed,
}) : super(key: key);
#override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabs>
with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
Tab(text: 'LEFT'),
Tab(text: 'RIGHT'),
];
TabController _tabController;
int _activeIndex = 0;
#override
void initState() {
super.initState();
_tabController = TabController(
vsync: this,
length: myTabs.length,
);
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
setState(() {
_activeIndex = _tabController.index;
});
}
});
return Container(
color: Colors.white,
child: TabBar(
controller: _tabController,
isScrollable: true,
indicatorPadding: EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0),
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), color: Colors.green),
tabs: myTabs
.map<Widget>((myTab) => Tab(
child: Container(
width: width / 3 -
10, // - 10 is used to make compensate horizontal padding
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color:
_activeIndex == myTabs.indexOf(myTab)
? Colors.transparent
: Color(0xffA4BDD4),
),
margin:
EdgeInsets.symmetric(horizontal: 5.0, vertical: 5.0),
child: Align(
alignment: Alignment.center,
child: Text(
myTab.text,
style: TextStyle(color: Colors.white),
),
),
),
))
.toList(),
onTap: widget.onItemPressed,
),
);
}
}
Thanks to the example of RĂ©mi Rousselet, you can do it, the code like this:
_tabController.index
This will return the current index of the position of your TabBarView
You can add a listener to listen to changes in tabs like below
tabController = TabController(vsync: this, length: 4)
..addListener(() {
setState(() {
switch(tabController.index) {
case 0:
// some code here
case 1:
// some code here
}
});
});
Well, nothing here was working in my case.
I tried several responses so as a result i used a provider to keep and retrieve the current index selected.
First the model.
class HomeModel extends ChangeNotifier {
int _selectedTabIndex = 0;
int get currentTabIndex => _selectedTabIndex;
setCurrentTabIndex(final int index){
_selectedTabIndex = index;
// notify listeners if you want here
notifyListeners();
}
...
}
Then i used _tabController.addListener() to update my model.
class HomePageState extends State<HomeScreen> {
late TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: _tabs.length);
_tabController.addListener(() {
context.read<HomeModel>().setCurrentTabIndex(_tabController.index);
});
}
...
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabs.length,
child :
Scaffold(
backgroundColor: Colors.white70,
appBar: AppBar(
/*iconTheme: IconThemeData(
color: Colors.black
),*/
bottom: TabBar(
controller: _tabController,
tabs: _tabs,
),
title: Text(_getAppBarTitle(),style: const TextStyle(/*color: Colors.red,*/fontSize: 22.0),)
...
)
)
);
}
...
}
Finally last but not least retrieve value when you need.
class _AppState extends State<App> {
#override
Widget build(BuildContext context) {
return Consumer<HomeModel>(
builder: (context, homeModel, child) {
return Text(homeModel.currentTabIndex); // herre we get the real current index
});
}
}
Set the variable in top.
class _MainTabWidgetState extends State<MainTabWidget> {
#override void initState() {
// TODO: implement initState
super.initState();
}
int selected_index = 0;
}
Now set index in Tabbar onTap
onTap: (index) {
setState(() {
selected_index = index;
});
},
This Code will give you index of Active tab , also save the tab index for future use, and when you back to the tab page the the previous active page will be displayed.
import 'package:flutter/material.dart';
void main() {
runApp(new TabBarDemo());
}
class TabBarDemo extends StatelessWidget {
TabScope _tabScope = TabScope.getInstance();
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new DefaultTabController(
length: 3,
index: _tabScope.tabIndex, //
child: new Scaffold(
appBar: new AppBar(
bottom: new TabBar(
onTap: (index) => _tabScope.setTabIndex(index), //current tab index
tabs: [
new Tab(icon: new Icon(Icons.directions_car)),
new Tab(icon: new Icon(Icons.directions_transit)),
new Tab(icon: new Icon(Icons.directions_bike)),
],
),
title: new Text('Tabs Demo'),
),
body: new TabBarView(
children: [
new Icon(Icons.directions_car),
new Icon(Icons.directions_transit),
new Icon(Icons.directions_bike),
],
),
),
),
);
}
}
class TabScope{ // singleton class
static TabScope _tabScope;
int tabIndex = 0;
static TabScope getInstance(){
if(_tabScope == null) _tabScope = TabScope();
return _tabScope;
}
void setTabIndex(int index){
tabIndex = index;
}
}
Related
Hi,
I am making a to-do/tasks app, I want the users to create their own categories/tabs for them to store notes under, I already can show the notes with the corresponding category but I'm stuck at actually displaying data from firebase as tabs in TabBar, I've created a List with hardcoded values, how do I get values from Firebase into my list and iterate them as actual tabs?
class HomeTopTabs extends StatefulWidget {
const HomeTopTabs({super.key});
#override
State<HomeTopTabs> createState() => _HomeTopTabs();
}
class _HomeTopTabs extends State<HomeTopTabs>
with SingleTickerProviderStateMixin {
final Stream<QuerySnapshot> categories =
FirebaseFirestore.instance.collection('categories').snapshots();
static const List<Tab> myTabs = <Tab>[
Tab(text: 'Spanish'),
Tab(text: 'Work'),
];
late TabController _tabController;
late final FirebaseCloudStorage _notesService;
String get userId => AuthService.firebase().currentUser!.id;
#override
void initState() {
super.initState();
_notesService = FirebaseCloudStorage();
_tabController = TabController(length: myTabs.length, vsync: this);
// _tabController.addListener(_handleTabSelection);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Container(
height: 500,
child: Scaffold(
appBar: AppBar(
flexibleSpace: SafeArea(
child: TabBar(
controller: _tabController,
isScrollable: true,
onTap: (index) {},
tabs: myTabs,
)),
),
body: TabBarView(
controller: _tabController,
children: myTabs.map((Tab tab) {
final String label = tab.text!.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
),
);
}
}
I have tried to search other questions and relating tutorials but most of them is depreciated code.
I have a TabBarthat show total of 16 catgories that shows dynamically,
Usecase:when i click on category any cateogry let's say i click on category 5 then if i press back button in phone i need to go to the category 1,as default i can show category 1 also if i press backbutton 2 time i need to close the app too
but i need to show category 1 when i press back button how do i do that, i have tried with willpopScope but nothing happens,This is what i tried so far
WillPopScope(
onWillPop: _onWillPop,
child: category_list.length != 0
? DefaultTabController(
length: category_list.length,
initialIndex: 1,
child: Column(
children: <Widget>[
Column(
children: [
Container(
constraints: BoxConstraints.expand(height: 40),
child: TabBar(
controller: _tabController,
isScrollable: true,
indicatorColor: Color(0xff00ADEE),
labelColor: Color(0xff00ADEE),
unselectedLabelColor: Colors.black,
tabs: getTab(),
),
),
],
),
Expanded(
child: Container(
child: TabBarView(
controller: _tabController,
children: createDynamicslugWIdget()),
),
)
],
),
)
: Center(
child: CircularProgressIndicator(),
),
)
Tried to return index 1
Future<bool> _onWillPop() async {
return _tabController.index==1;
}
You can copy paste run full code below
I use official example to simulate this case
You can set _tabController.index
code snippet
Future<bool> _onWillPop() async {
print("on will pop");
if (_tabController.index == 0) {
await SystemNavigator.pop();
}
Future.delayed(Duration(milliseconds: 200), () {
print("set index");
_tabController.index = 0;
});
print("return");
return _tabController.index == 0;
}
working demo
full code
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({Key key}) : super(key: key);
#override
_MyTabbedPageState createState() => _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage>
with SingleTickerProviderStateMixin {
final List<Tab> myTabs = <Tab>[
Tab(text: '0'),
Tab(text: '1'),
Tab(text: '2'),
Tab(text: '3'),
Tab(text: '4'),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: myTabs.length);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
Future<bool> _onWillPop() async {
print("on will pop");
if (_tabController.index == 0) {
await SystemNavigator.pop();
}
Future.delayed(Duration(milliseconds: 200), () {
print("set index");
_tabController.index = 0;
});
print("return");
return _tabController.index == 0;
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: myTabs,
),
),
body: TabBarView(
controller: _tabController,
children: myTabs.map((Tab tab) {
final String label = tab.text.toLowerCase();
return Center(
child: Text(
'This is the $label tab',
style: const TextStyle(fontSize: 36),
),
);
}).toList(),
),
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyTabbedPage(),
);
}
}
I am using a tab controller in flutter, but how do i able to navigate to a certain tab screen with a button click. I put my tab controller in my main screen then 3 different screens. Below is my example codes.
Main Screen (with tab controller)
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
int _currentIndex = 0;
TabController _tabController;
final List<Widget> _children = [
firstscreen.FirstScreen(),
secondscreen.SecondScreen(),
thirdscreen.ThirdScreen()
];
List<Widget> _tabs = <Widget>[
Tab(icon: Icon(Icons.home), text: 'Home'),
Tab(icon: Icon(Icons.history), text: 'History'),
Tab(icon: Icon(Icons.account_circle), text: 'Profile'),
];
#override
void initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: _onWillPop,
child: Scaffold(
appBar: AppBar(
title: Text("My Title"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.account_balance_wallet),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => Data Screen()));
},
),
],
bottom: TabBar(
controller: _tabController,
tabs: _tabs,
),
),
body: TabBarView(
controller: _tabController,
children: _children,
),
),
);
}
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
First Screen
class FirstScreen extends StatefulWidget {
FirstScreen({Key key}) : super(key: key);
#override
FirstScreenState createState() {
return new FirstScreenState();
}
}
class FirstScreenState extends State<FirstScreen>
with AutomaticKeepAliveClientMixin<FirstScreen> {
Widget get historyCard {
return Container(
height: 280,
width: MediaQuery.of(context).size.width / 1,
padding: EdgeInsets.only(top: 10.0, left: 20.0, right: 20.0),
child: Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
child: Stack(
children: <Widget>[
ClipPath(
clipper: ShapeBorderClipper(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
),
),
Stack(alignment: Alignment.center, children: <Widget>[
Positioned(
top: 10,
left: 10,
child: Text("RECENT ACTIVITY",
style: TextStyle(
fontSize: 14.0,
)),
),
Positioned(
top: 0,
right: 0,
child: FlatButton(
child: Text('MORE >'),
onPressed: () => {},
textColor: Colors.blueAccent, // JUMP TO SECOND TAB or ANY
//OTHER TAB
),
),
]),
],
),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
historyCard,
]),
);
}
}
So right now i'm not sure how do i able to navigate to any tab with button click, because i place my tab bar in MainScreen.dartor must i change the FirstPage code into the MainScreen.dart, then only can click and navigate?
You can set index in TabController to change current tab as below:
In below example, one button is in TabBarView screen in "MyFirstTab", from that button press we call the changeMyTab() of parent class which is "StateKeeper".
import 'package:flutter/material.dart';
class MyTabController extends StatefulWidget {
createState() {
return StateKeeper();
}
}
class StateKeeper extends State<MyTabController> with SingleTickerProviderStateMixin {
TabController _tabController;
final List<Tab> myTabs = <Tab>[
new Tab(icon: Icon(Icons.directions_car),),
new Tab(icon: Icon(Icons.directions_bike),),
new Tab(icon: Icon(Icons.directions_boat),),
new Tab(icon: Icon(Icons.directions_railway),),
];
#override
void initState() {
// TODO: implement initState
super.initState();
_tabController = new TabController(vsync: this, length: myTabs.length);
}
changeMyTab(){
setState(() {
_tabController.index = 2;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(tabs: myTabs, controller: _tabController,),
title: Text('Tabs Demo'),
),
body: TabBarView(controller: _tabController, children: [
MyFirstTab(onTabChangeCallback: () => {
changeMyTab()
},),
Icon(Icons.directions_bike),
Icon(Icons.directions_boat),
Icon(Icons.directions_railway),
]),
),
),
);
}
}
class MyFirstTab extends StatefulWidget {
const MyFirstTab({this.onTabChangeCallback});
final TabChangeCallback onTabChangeCallback;
createState() {
return MyFirstTabStateKeeper(onTabChangeCallback);
}
}
class MyFirstTabStateKeeper extends State<MyFirstTab> {
TabChangeCallback onTabChangeCallback;
MyFirstTabStateKeeper(TabChangeCallback onTabChangeCallback){
this.onTabChangeCallback = onTabChangeCallback;
}
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
RaisedButton(
child: Text('Change Tab'),
onPressed: onTabChangeCallback,
),
],
);
}
}
typedef TabChangeCallback = void Function();
So I have a Flutter application with multiple pages, this is done via a PageView. Before this page view I create my AppBar so it is persistent at the top of the application and doesn't animate when scrolling between pages.
I then want on one of the pages to create a bottom App bar, but for that I need to access the App bar element, however I have no idea how to do this.
This is the main class, the page I am trying to edit the app bar on is PlanPage.
final GoogleSignIn googleSignIn = GoogleSignIn();
final FirebaseAuth auth = FirebaseAuth.instance;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: '',
home: _handleCurrentScreen()
);
}
Widget _handleCurrentScreen() {
return StreamBuilder<FirebaseUser>(
stream: auth.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
print(snapshot);
if (snapshot.connectionState == ConnectionState.waiting) {
return SplashPage();
} else {
if (snapshot.hasData) {
return Home();
}
return LoginPage();
}
}
);
}
}
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
class HomeState extends State<Home> {
PageController _pageController;
PreferredSizeWidget bottomBar;
int _page = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: bottomBar,
),
body: PageView(
children: [
Container(
child: SafeArea(
child: RecipesPage()
),
),
Container(
child: SafeArea(
child: PlanPage()
),
),
Container(
child: SafeArea(
child: ShoppingListPage()
),
),
Container(
child: SafeArea(
child: ExplorePage()
),
),
],
/// Specify the page controller
controller: _pageController,
onPageChanged: onPageChanged
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.book),
title: Text('Recipes')
),
BottomNavigationBarItem(
icon: Icon(Icons.event),
title: Text('Plan')
),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_cart),
title: Text('Shopping List')
),
BottomNavigationBarItem(
icon: Icon(Icons.public),
title: Text("Explore"),
),
],
onTap: navigationTapped,
currentIndex: _page,
),
);
}
void onPageChanged(int page){
setState((){
this._page = page;
});
}
void setBottomAppBar(PreferredSizeWidget appBar) {
this.bottomBar = appBar;
print("setBottomAppBar: "+ appBar.toString());
}
/// Called when the user presses on of the
/// [BottomNavigationBarItem] with corresponding
/// page index
void navigationTapped(int page){
// Animating to the page.
// You can use whatever duration and curve you like
_pageController.animateToPage(
page,
duration: const Duration(milliseconds: 300),
curve: Curves.ease
);
}
#override
void initState() {
super.initState();
initializeDateFormatting();
_pageController = PageController();
}
#override
void dispose(){
super.dispose();
_pageController.dispose();
}
}
The PlanPage class looks like this
class PlanPage extends StatefulWidget {
var homeState;
PlanPage(this.homeState);
#override
State<StatefulWidget> createState() {
return _PlanState(homeState);
}
}
class _PlanState extends State<PlanPage> with AutomaticKeepAliveClientMixin<PlanPage>, SingleTickerProviderStateMixin {
var homeState;
TabController _tabController;
_PlanState(this.homeState);
#override
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
//homeState.setBottomAppBar(_buildTabBar());
return Scaffold(
appBar: AppBar(
bottom: _buildTabBar(),
),
body: TabBarView(
controller: _tabController,
children: Plan.now().days.map((day) {
return ListView.builder(
itemCount: MealType.values.length,
itemBuilder: (BuildContext context, int index){
var mealType = MealType.values[index];
return Column(
children: <Widget>[
Text(
mealType.toString().substring(mealType.toString().indexOf('.')+1),
style: TextStyle(
//decoration: TextDecoration.underline,
fontSize: 30.0,
fontWeight: FontWeight.bold
),
),
Column(
children: day.meals.where((meal) => meal.mealType == mealType).map((meal) {
return RecipeCard(meal.recipe);
}).toList(),
)
],
);
}
);
}).toList(),
)
);
}
Widget _buildTabBar() {
return TabBar(
controller: _tabController,
isScrollable: true,
tabs: List.generate(Plan.now().days.length,(index) {
return Tab(
child: Column(
children: <Widget>[
Text(DateFormat.E().format(Plan.now().days[index].day)),
Text(DateFormat('d/M').format(Plan.now().days[index].day)),
],
),
);
}, growable: true),
);
}
#override
void initState() {
super.initState();
_tabController = new TabController(
length: Plan.now().days.length,
vsync: this,
initialIndex: 1
);
}
}
However the way it works now, makes it show 2 app bars.[
Usually it's a not a best practice to have two nested scrollable areas. Same for two nested Scaffolds.
That said, you can listen to page changes ( _pageController.addListener(listener) ) to update a page state property, and build a different AppBar.bottom (in the Home widget, so you can remove the Scaffold in PlanPage) depending on the page the user is viewing.
-EDIT-
In your Home widget you can add a listener to the _pageController like so:
void initState() {
super.initState();
_pageController = PageController()
..addListener(() {
setState(() {});
});
}
to have your widget rebuilt every time the user scrolls within your PageView. The setState call with an empty function might looks confusing, but it simply allows you to have the widget rebuilt when _pageController.page changes, which is not the default behavior. You could also have a page state property and update it in the setState call to reflect the _pageController.page property, but the result would be the same.
This way you can build a different AppBar.bottom depending on the _pageController.page:
// in your build function
final bottomAppBar = _pageController.page == 2 ? TabBar(...) : null;
final appBar = AppBar(
bottom: bottomAppBar,
...
);
return Scaffold(
appBar: appBar,
...
);
How can I disable TabView animation when Tab in TabBar clicked ?
I added
physics: NeverScrollableScrollPhysics()
for TabView but that doesn't apply for TabBar.
I'm using DefaultTabController.
Based on a very good answer on github about this issue, which achieves something similar to what your looking for (but with a bottomNavigationBar) here I share with you another workaround. It consists of combining a DefaultTabController with a PageView, a PageController and a simple index. Try this out.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tabs with no animation',
theme: ThemeData.dark(),
home: HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PageController _pageController;
final int currentTab = 0;
#override
void initState() {
// TODO: implement initState
_pageController = PageController(initialPage: currentTab);
super.initState();
}
final List<Tab> myTabs = <Tab>[
Tab(text: 'One'),
Tab(
text: 'Two',
),
];
var tabs = [
TabOne(),
TabTwo(),
];
#override
Widget build(BuildContext context) {
var pageView = PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: tabs,
);
return DefaultTabController(
length: myTabs.length,
child: Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0.0,
automaticallyImplyLeading: false,
title: Center(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.grey.shade800,
),
width: 200,
height: 50,
child: TabBar(
onTap: (index) {
_pageController.jumpToPage(index);
},
unselectedLabelColor: Colors.white,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(30),
color: Colors.black),
tabs: myTabs,
),
),
),
),
body: pageView),
);
}
}
class TabOne extends StatelessWidget {
const TabOne({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Center(child: Text('Tab one')),
);
}
}
class TabTwo extends StatelessWidget {
const TabTwo({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Center(child: Text('Tab two')),
);
}
}
Doing so, you have a something identical to a TabBarView but without animation.
I don't think there's a way to disable the transition animation on TabBarView. As a workaround, you can use a Container that'll return different pages depending on the tab selected.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
#override
void initState() {
super.initState();
tabController = TabController(length: 4, vsync: this);
}
var _homeScaffoldKey = Key("Scaffold Key");
var tabController;
var currentPage = 0;
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _homeScaffoldKey,
body: _getCustomContainer(),
bottomNavigationBar: new Material(
color: Colors.blue,
child: new TabBar(
isScrollable: true,
indicatorColor: Color.fromRGBO(255, 25, 255, 0.0),
controller: tabController,
onTap: (value) {
setState(() {
currentPage = value;
});
},
tabs: <Widget>[
new Tab(
icon: new Icon(Icons.accessibility),
),
new Tab(
icon: new Icon(Icons.accessibility),
),
new Tab(
icon: new Icon(Icons.accessibility),
),
new Tab(
icon: new Icon(Icons.accessibility),
),
],
),
),
);
}
_getCustomContainer() {
switch (currentPage) {
case 0:
return page1();
case 1:
return page2();
case 2:
return page3();
case 3:
return page4();
}
}
page1() => Container(
color: Colors.redAccent,
child: Center(
child: Text("Page 1"),
),
);
page2() => Container(
color: Colors.greenAccent,
child: Center(
child: Text("Page 2"),
),
);
page3() => Container(
color: Colors.blueAccent,
child: Center(
child: Text("Page 3"),
),
);
page4() => Container(
color: Colors.yellowAccent,
child: Center(
child: Text("Page 4"),
),
);
}
Demo
Seems like this can be achieved using DefaultTabController easily as of 2022.
Here is my solution to this:
class _TabPageState extends State<TabPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
#override
void initState() {
super.initState();
// when initializing the `TabController` set `animationDuration` as `zero`.
_tabController =
TabController(length: 3, vsync: this, animationDuration: Duration.zero);
}
#override
Widget build(BuildContext context) {
return Container(
color: ColorPalette.white,
child: SafeArea(
top: false,
child: DefaultTabController(
length: 3,
child: Builder(builder: (context) {
return Scaffold(
bottomNavigationBar: TabBar(
controller: _tabController, // set the tab controller of your `TabBar`
enableFeedback: false,
onTap: (index) {
setState(() {});
},
indicatorColor: Colors.transparent,
tabs: [
TabItem(
selectedIndex: _tabController.index,
index: 0,
assetName: Assets.tabHome),
TabItem(
selectedIndex: _tabController.index,
index: 1,
assetName: Assets.tabCare),
TabItem(
selectedIndex: _tabController.index,
index: 2,
assetName: Assets.tabProfile),
],
),
body: Center(
child: TabBarView(
controller: _tabController, // set the controller of your `TabBarView`
physics: const NeverScrollableScrollPhysics(),
children: const [
ParentHomePage(),
ParentCarePage(),
ParentAccountPage()
],
),
),
);
}),
),
),
);
}
}
You Can Fix It by Go to MaterialApp and type
theme:new ThemeData(
splashColor:Colors.blue,
highlightColor: Colors.blue
)
what it mean if your tab background color blue you will change splashColor and highlightColor to blue that mean the animation doesn't disabled but it doesn't appear because the animation splashcolor and highlight will be blue such as Tab Background , I Hope I Help You