I need to know which Tab is clicked. Therefore I added the SingleTickerProviderStateMixin
, created a TabController field in my State and added a Listener (huge boilerplate IMHO...).
class Home extends StatefulWidget {
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = new TabController(length: 2, vsync: this);
_tabController.addListener(() {
if (_tabController.indexIsChanging) {
print('click, ${_tabController.index}');
}
});
}
@override
Widget build(BuildContext context) {
//...
}
}
However every time I click a Tab, multiple statements are printed instead of just one, as I expected. Why is indexIsChanging
not working?
Warp your tab in BottomNavigationBar . it will give you option onTap() where you can check which tab will clicked.
To create a tab in it, create a tab list and create an object of its TabController. TabController _tabController; Initalize the TabController inside the initState() method and also override it in the dispose() method. The CustomTabBar results can be seen in the image.
By using DefaultTabController, you can get the current index easily whether the user changes tabs by Swiping or Tapping on the tab bar. You must wrap your Scaffold inside of a Builder and you can then retrieve the tab index with DefaultTabController. of(context).
Use IgnorePointer. It will disable the click action of tabs.
Reason From https://github.com/flutter/flutter/issues/13848
Source code of TabController
, notifyListeners()
call twice https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/material/tab_controller.dart#L162
if (duration != null) {
_indexIsChangingCount += 1;
notifyListeners(); // Because the value of indexIsChanging may have changed.
_animationController
.animateTo(_index.toDouble(), duration: duration, curve: curve)
.whenCompleteOrCancel(() {
_indexIsChangingCount -= 1;
notifyListeners();
});
Solution To print current index only one time
code snippet from https://github.com/flutter/flutter/issues/13848#issuecomment-486051402
@override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: choices.length)
..addListener(() {
if(_tabController.indexIsChanging) {
print("tab is animating. from active (getting the index) to inactive(getting the index) ");
}else {
//tab is finished animating you get the current index
//here you can get your index or run some method once.
print(_tabController.index);
}
});
}
working demo
full test code
import 'package:flutter/material.dart';
class AppBarBottomSample extends StatefulWidget {
@override
_AppBarBottomSampleState createState() => _AppBarBottomSampleState();
}
class _AppBarBottomSampleState extends State<AppBarBottomSample>
with SingleTickerProviderStateMixin {
TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: choices.length)
..addListener(() {
if(_tabController.indexIsChanging) {
print("tab is animating. from active (getting the index) to inactive(getting the index) ");
}else {
//tab is finished animating you get the current index
//here you can get your index or run some method once.
print(_tabController.index);
}
});
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _nextPage(int delta) {
final int newIndex = _tabController.index + delta;
if (newIndex < 0 || newIndex >= _tabController.length) return;
_tabController.animateTo(newIndex);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('AppBar Bottom Widget'),
leading: IconButton(
tooltip: 'Previous choice',
icon: const Icon(Icons.arrow_back),
onPressed: () {
_nextPage(-1);
},
),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_forward),
tooltip: 'Next choice',
onPressed: () {
_nextPage(1);
},
),
],
bottom: PreferredSize(
preferredSize: const Size.fromHeight(48.0),
child: Theme(
data: Theme.of(context).copyWith(accentColor: Colors.white),
child: Container(
height: 48.0,
alignment: Alignment.center,
child: TabPageSelector(controller: _tabController),
),
),
),
),
body: TabBarView(
controller: _tabController,
children: choices.map((Choice choice) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: ChoiceCard(choice: choice),
);
}).toList(),
),
),
);
}
}
class Choice {
const Choice({this.title, this.icon});
final String title;
final IconData icon;
}
const List<Choice> choices = const <Choice>[
const Choice(title: 'CAR', icon: Icons.directions_car),
const Choice(title: 'BICYCLE', icon: Icons.directions_bike),
const Choice(title: 'BOAT', icon: Icons.directions_boat),
const Choice(title: 'BUS', icon: Icons.directions_bus),
const Choice(title: 'TRAIN', icon: Icons.directions_railway),
const Choice(title: 'WALK', icon: Icons.directions_walk),
];
class ChoiceCard extends StatelessWidget {
const ChoiceCard({Key key, this.choice}) : super(key: key);
final Choice choice;
@override
Widget build(BuildContext context) {
final TextStyle textStyle = Theme.of(context).textTheme.display1;
return Card(
color: Colors.white,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(choice.icon, size: 128.0, color: textStyle.color),
Text(choice.title, style: textStyle),
],
),
),
);
}
}
void main() {
runApp(AppBarBottomSample());
}
To prevent calling a method twice, you can add this condition,
if(!controller.indexIsChanging){ //perform your operation }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With