This is what I am trying to achieve.
I tried adding a Stack to my bottom navigation bar item, and using negative values in a Positioned widget, but this doesn't work as it gets cutoff at the top of the navigation bar.
Here is the code for my BottomNavigationBarItem. Right now I am using just a red dot to try and get it above the button.
new BottomNavigationBarItem(
icon: new Stack(
overflow: Overflow.visible,
children: <Widget>[
new Icon(Icons.home),
new Positioned(
top: -5.0,
right: 0.0,
child: new Icon(Icons.brightness_1, size: 8.0,
color: Colors.redAccent),
)
]
),
title: new Container(),
backgroundColor: Colors.white),
You can try this
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,
canvasColor: 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> {
ValueNotifier<int> bottomNavNotifier = new ValueNotifier(0);
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
bottomNavigationBar: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new BottomNavHighlight(bottomNavNotifier),
new BottomNavigationBar(
type: BottomNavigationBarType.fixed,
fixedColor: Colors.white,
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(icon: new Icon(Icons.create), title: new Text("Create")),
new BottomNavigationBarItem(icon: new Icon(Icons.create), title: new Text("Create")),
new BottomNavigationBarItem(icon: new Icon(Icons.create), title: new Text("Create")),
new BottomNavigationBarItem(icon: new Icon(Icons.create), title: new Text("Create"))
],
onTap: (int index){
print(index);
bottomNavNotifier.value = index;
},
)
],
),
backgroundColor: Colors.white,// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class BottomNavHighlight extends StatefulWidget{
final ValueNotifier<int> activeIndex;
BottomNavHighlight(this.activeIndex);
#override
State createState() {
return new _BottomNavHighlightState();
}
}
class _BottomNavHighlightState extends State<BottomNavHighlight>{
#override
Widget build(BuildContext context) {
List<Widget> items = <Widget>[
new Expanded(child: new Container()),
new Expanded(child: new Container()),
new Expanded(child: new Container()),
];
items.insert(
widget.activeIndex.value,
new Expanded(child: new Container(child: new Icon(Icons.play_circle_outline, size: 40.0,))),);
return new Row(
children: items,
);
}
#override
void initState() {
super.initState();
widget.activeIndex.addListener((){
setState(() {
});
});
}
#override
void dispose() {
super.dispose();
widget.activeIndex.dispose();
}
}
import 'package:flutter/material.dart';
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
TabController tabController;
int _index = 0;
List<String> tabString = [
'Discover',
'Geners',
'Artists'
];
String _title = 'Discover';
#override
void initState() {
super.initState();
tabController = TabController(
length: 3,
vsync: this
);
this._index = 0;
setState(() {
this._title = tabString.first;
});
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: (){},
)
],
),
body: new Container(
color: const Color(0xffEEEEEE),
child: TabBarView(
children: <Widget>[
Container(child:Text('1')),
Container(child:Text('2')),
Container(child:Text('3')),
],
controller: tabController,
),
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _index,
onTap: (int _index) {
setState(() {
this._title = tabString[_index];
this._index = _index;
this.tabController.animateTo(_index);
});
},
items: <BottomNavigationBarItem>[
new BottomNavigationBarItem(
icon: new Icon(Icons.dashboard),
title: new Text("Discover"),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.surround_sound),
title: new Text("Geners"),
),
new BottomNavigationBarItem(
icon: new Icon(Icons.account_circle),
title: new Text("Artists"),
),
]),
);
}
}
Check full code here
https://github.com/santoshanand/flutter_movie
Related
I have a Bottom Navigation in parent widget, and a few textfields in child widget. When user clicks on the navigation tab and if one of the textfields is empty, it will set focus on the particular textfields.
I am using the constructor method learnt from one of the developer however I couldn't get it work. It seems like I didn't pass over the context properly. I am not sure.
Anyone able to spot my mistakes or advise other methods which can achieve the same result?
login.dart
class Login extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return _LoginState();
}
}
class _LoginState extends State<Login> {
FocusNode focusNode;
Page1 focus;
#override
void initState() {
super.initState();
focusNode = new FocusNode();
focus = new Page1(focusNode: focusNode);
}
int currentBottomNavIndex = 0;
List<Widget> bottomNav = [
Page1(),
Page2(),
];
onTapped(int index) {
//if(textfield not empty) {
//setState(() {
//currentBottomNavIndex = index;
//});
//}else {
focus.setFocus(context);
//}
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: Text('Login Page'),
),
body: bottomNav[currentBottomNavIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTapped,
//onTap: requestFocus(context),
currentIndex: currentBottomNavIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Page1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.mail),
title: Text('Page2'),
),
],
),
);
}
}
page1.dart
class Page1 extends StatefulWidget {
final FocusNode focusNode;
const Page1({Key key, this.focusNode}) : super(key: key);
void setFocus(BuildContext context) {
print("$focusNode requestFocus...");
FocusScope.of(context).requestFocus(focusNode);
}
#override
State<StatefulWidget> createState() {
return _Page1State();
}
}
class _Page1State extends State<Page1> {
TextEditingController name1 = TextEditingController();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: SingleChildScrollView(
child: Column(
children: <Widget>[
nameApp(),
],
)
)
);
}
Widget nameApp(){
return Container(
margin: EdgeInsets.all(50.0),
//width: 185,
child: Center(
child: Row(
children: [
Container(
child: Text("Name :", style: TextStyle(fontSize: 15), ),
),
Container(
child: Flexible(
child: TextField(
focusNode: widget.focusNode,
controller: name1,
onTap: (){
name1.clear();
},
onChanged: (String str){
},
textAlign: TextAlign.center,
decoration: InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 5),
hintText: "Full Name",
hintStyle: TextStyle(fontSize: 14),
),
),
),
),
]
)
)
);
}
}
When user click on the bottom tab, I expect to see the textfield is in focus however nothing happen.
I noticed the method in child widget has been called:
flutter: FocusNode#419f4 requestFocus...
flutter: FocusNode#419f4(FOCUSED) requestFocus...
however the textfield is still not focus.
I've create a simple sample project for this and its works for me just fine.
Please check out my solution:
The HomePage:
import 'package:flutter/material.dart';
import 'package:focus_node/widgets/MyInputWidget.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(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> {
FocusNode field1FocusNode = FocusNode(); //Create first FocusNode
FocusNode field2FocusNode = FocusNode(); //Create second FocusNode
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 35),
child: MyInputWidget(
focusNode: field1FocusNode, //Provide the first FocusNode in the constructor
hint: "Email",
onEditCompleted: (){
FocusScope.of(context).requestFocus(field2FocusNode); //Request focus
},
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 35),
child: MyInputWidget(
focusNode: field2FocusNode, //Provide the second FocusNode
hint: "Password",
onEditCompleted: (){
FocusScope.of(context).requestFocus(field1FocusNode); //Request focus
},
),
)
],
),
),
);
}
}
The Custom Widget required focus:
class MyInputWidget extends StatefulWidget {
final FocusNode focusNode;
final String hint;
final VoidCallback onEditCompleted;
MyInputWidget({this.focusNode, this.hint, this.onEditCompleted});
#override
_MyInputWidgetState createState() => _MyInputWidgetState();
}
class _MyInputWidgetState extends State<MyInputWidget> {
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(8),
child: TextField(
focusNode: widget.focusNode, //The FocusNode provided by the parent widget
decoration: InputDecoration(
hintText: widget.hint
),
onEditingComplete: widget.onEditCompleted,
),
);
}
}
Hope this helps.
I need to detect TabBar when I swipe then print somethings on console, how I can do that? This is my code.
bottomNavigationBar: new Material(
color: Colors.blueAccent,
child: new TabBar(
onTap: (int index){ setState(() {
_onTap(index);
});},
indicatorColor: Colors.white,
controller: controller,
tabs: <Widget>[
new Tab(icon: new Icon(Icons.shopping_basket)),
new Tab(icon: new Icon(Icons.store)),
new Tab(icon: new Icon(Icons.local_offer)),
new Tab(icon: new Icon(Icons.assignment)),
new Tab(icon: new Icon(Icons.settings)),
],
)
),
You need to add a listener to your tab controller - maybe in initState.
controller.addListener((){
print('my index is'+ controller.index.toString());
});
Swipe functionality is not provided by onTap() function, for that TabController is used
class TabBarDemo extends StatefulWidget {
#override
_TabBarDemoState createState() => _TabBarDemoState();
}
class _TabBarDemoState extends State<TabBarDemo>
with SingleTickerProviderStateMixin {
TabController _controller;
int _selectedIndex = 0;
List<Widget> list = [
Tab(icon: Icon(Icons.card_travel)),
Tab(icon: Icon(Icons.add_shopping_cart)),
];
#override
void initState() {
// TODO: implement initState
super.initState();
// Create TabController for getting the index of current tab
_controller = TabController(length: list.length, vsync: this);
_controller.addListener(() {
setState(() {
_selectedIndex = _controller.index;
});
print("Selected Index: " + _controller.index.toString());
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
bottom: TabBar(
onTap: (index) {
// Should not used it as it only called when tab options are clicked,
// not when user swapped
},
controller: _controller,
tabs: list,
),
title: Text('Tabs Demo'),
),
body: TabBarView(
controller: _controller,
children: [
Center(
child: Text(
_selectedIndex.toString(),
style: TextStyle(fontSize: 40),
)),
Center(
child: Text(
_selectedIndex.toString(),
style: TextStyle(fontSize: 40),
)),
],
),
),
);
}
}
Github Repo:
https://github.com/jitsm555/Flutter-Problems/tree/master/tab_bar_tricks
Output:
Here is a full example. Use a TabController and add a callback using addListener.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: MyTabbedPage(),
);
}
}
class MyTabbedPage extends StatefulWidget {
const MyTabbedPage({Key key}) : super(key: key);
#override
_MyTabbedPageState createState() => _MyTabbedPageState();
}
class _MyTabbedPageState extends State<MyTabbedPage> with SingleTickerProviderStateMixin {
var _context;
final List<Tab> myTabs = <Tab>[
Tab(text: 'LEFT'),
Tab(text: 'RIGHT'),
];
TabController _tabController;
#override
void initState() {
super.initState();
_tabController = TabController(vsync: this, length: myTabs.length);
_tabController.addListener(_handleTabSelection);
}
void _handleTabSelection() {
if (_tabController.indexIsChanging) {
switch (_tabController.index) {
case 0:
Scaffold.of(_context).showSnackBar(SnackBar(
content: Text('Page 1 tapped.'),
duration: Duration(milliseconds: 500),
));
break;
case 1:
Scaffold.of(_context).showSnackBar(SnackBar(
content: Text('Page 2 tapped.'),
duration: Duration(milliseconds: 500),
));
break;
}
}
}
#override
void dispose() {
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: myTabs,
),
),
body: Builder(
builder: (context) {
_context = context;
return 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(),
);
},
),
);
}
}
You can create wrapper widget
class _DefaultTabControllerListener extends StatefulWidget {
const _DefaultTabControllerListener(
{Key? key, this.onTabSelected, required this.child})
: super(key: key);
final void Function(int index)? onTabSelected;
final Widget child;
#override
_DefaultTabControllerListenerState createState() =>
_DefaultTabControllerListenerState();
}
class _DefaultTabControllerListenerState
extends State<_DefaultTabControllerListener> {
late final void Function()? _listener;
TabController? _tabController;
#override
void initState() {
super.initState();
WidgetsBinding.instance?.addPostFrameCallback((_) {
final tabController = DefaultTabController.of(context)!;
_listener = () {
final onTabSelected = widget.onTabSelected;
if (onTabSelected != null) {
onTabSelected(tabController.index);
}
};
tabController.addListener(_listener!);
});
}
#override
void didChangeDependencies() {
_tabController = DefaultTabController.of(context);
super.didChangeDependencies();
}
#override
void dispose() {
if (_listener != null && _tabController != null) {
_tabController!.removeListener(_listener!);
}
super.dispose();
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
And wrap TabBar with this widget
DefaultTabController(
child: _DefaultTabControllerListener(
onTabSelected: (index) {
// Handler
},
child: TabBar(.....
We can listen to tab scroll notification by using NotificationListener Widget
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: NotificationListener(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollUpdateNotification) {
_onStartScroll(scrollNotification.metrics);
}
},
child: _buildTabBarAndTabBarViews(),
);
}
_onStartScroll(ScrollMetrics metrics) {
print('hello world');
}
}
I ran into a similar issue and following is what I did to accomplish the same: -
import 'package:flutter/material.dart';
import '../widgets/basic_dialog.dart';
import 'sign_in_form.dart';
import 'sign_up_form.dart';
class LoginDialog extends StatefulWidget {
#override
_LoginDialogState createState() => _LoginDialogState();
}
class _LoginDialogState extends State<LoginDialog>
with SingleTickerProviderStateMixin {
int activeTab = 0;
TabController controller;
#override
void initState() {
super.initState();
controller = TabController(vsync: this, length: 2);
}
#override
Widget build(BuildContext context) {
return NotificationListener(
onNotification: (ScrollNotification notification) {
setState(() {
if (notification.metrics.pixels <= 100) {
controller.index = 0;
} else {
controller.index = 1;
}
});
return true;
},
child: BasicDialog(
child: Container(
height: controller.index == 0
? MediaQuery.of(context).size.height / 2.7
: MediaQuery.of(context).size.height / 1.8,
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TabBar(
controller: controller,
tabs: <Widget>[
Padding(
padding: const EdgeInsets.all(5.0),
child: Text('Sign In'),
),
Padding(
padding: const EdgeInsets.all(5.0),
child: Text('Sign up'),
),
],
),
Expanded(
child: TabBarView(
controller: controller,
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: SignInForm(),
),
),
// If a container is not displayed during the tab switch to tab0, renderflex error is thrown because of the height change.
controller.index == 0
? Container()
: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: SignUpForm(),
),
),
],
),
)
],
),
),
),
);
}
}
If you are using DefaultTabController and want to listen to updates in TabBar, you can expose the controller using the DefaultTabController.of method and then add a listener to it:
DefaultTabController(
length: 3,
child: Builder(
builder: (BuildContext context) {
final TabController controller = DefaultTabController.of(context)!;
controller.addListener(() {
if (!controller.indexIsChanging) {
print(controller.index);
// add code to be executed on TabBar change
}
});
return Scaffold(...
Here you have a full example:
class TabControllerDemo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3,
child: Builder(builder: (BuildContext context) {
final TabController controller = DefaultTabController.of(context)!;
controller.addListener(() {
if (!controller.indexIsChanging) {
print(controller.index);
// add code to be executed on TabBar change
}
});
return Scaffold(
appBar: AppBar(
bottom: const TabBar(
tabs: [
Tab(text: "Tab 0"),
Tab(text: "Tab 1"),
Tab(text: "Tab 2"),
],
),
title: const Text('Tabs Demo'),
),
body: const TabBarView(
children: [
Center(child: Text('View 0')),
Center(child: Text('View 1')),
Center(child: Text('View 2')),
],
),
);
})),
);
}
}
You can also check this DartPad LiveDemo.
Warp your tab in BottomNavigationBar . it will give you option onTap() where you can check which tab will clicked.
using this code you will also redirect to particular page when you tap on Tab
import 'package:flutter/material.dart';
class BottomBarList extends StatefulWidget {
#override
_BottomBarListState createState() => _BottomBarListState();
}
class _BottomBarListState extends State<BottomBarList> {
int bottomSelectedIndex = 0;
int _selectedIndex = 0;
List<Widget> _widgetOptions = <Widget>[
AllMovieList(),
MovieDescription(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(),
bottomNavigationBar: bottomBar(),
body:_widgetOptions.elementAt(_selectedIndex),
);
}
bottomBar() {
return BottomNavigationBar(
type: BottomNavigationBarType.shifting,
unselectedItemColor: Colors.grey,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.tv),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Icon(Icons.star),
title: Text('Business'),
),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.black,
backgroundColor: Colors.orange,
onTap: _onItemTapped,
);
}
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
}
You can use the only scrollNotification (ScrollEndNotification) of the NotificationListener. It covers the tap and swipe actions.
class HandlingTabChanges extends State<JsonTestDetailFrame> with SingleTickerProviderStateMixin {
late final TabController _tabController;
final int _tabLength = 2;
#override
void initState() {
super.initState();
_tabController = TabController(length: _tabLength, vsync: this);
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabLength,
child: Scaffold(
appBar: AppBar(
title: Text("Some title"),
bottom: TabBar(
controller: _tabController,
tabs: [
Icon(Icons.settings),
Icon(Icons.list_alt),
],
),
),
body: NotificationListener(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification) _onTabChanged();
return false;
},
child: TabBarView(
controller: _tabController,
children: [
SomeWidget1(),
SomeWidget2(),
],
),
),
),
);
}
void _onTabChanged() {
switch (_tabController.index) {
case 0:
// handle 0 position
break;
case 1:
// handle 1 position
break;
}
}
}
You can disable swiping effect on TabBarView by adding:
physics: NeverScrollableScrollPhysics(),
and declaring one TabController and assigning that to your TabBar and TabBarView:
TabController _tabController;
I am creating an app that contains a tab bar on its homepage. I want to be able to navigate to one of the tabs using my FloatingActionButton. In addition, I want to keep the default methods of navigating to that tab, i.e. by swiping on screen or by clicking the tab.
I also want to know how to link that tab to some other button.
Here is a screenshot of my homepage.
You need to get the TabBar controller and call its animateTo() method from the button onPressed() handle.
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',
home: new MyTabbedPage(),
);
}
}
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(
title: new Text("Tab demo"),
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(),
),
floatingActionButton: new FloatingActionButton(
onPressed: () => _tabController.animateTo((_tabController.index + 1) % 2), // Switch tabs
child: new Icon(Icons.swap_horiz),
),
);
}
}
If you use a GlobalKey for the MyTabbedPageState you can get the controller from any place, so you can call the animateTo() from any button.
class MyApp extends StatelessWidget {
static final _myTabbedPageKey = new GlobalKey<_MyTabbedPageState>();
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyTabbedPage(
key: _myTabbedPageKey,
),
);
}
}
You could call it from anywhere doing:
MyApp._myTabbedPageKey.currentState._tabController.animateTo(...);
I am super late, but hopefully someone benefits from this. just add this line to your onPressed of your button and make sure to change the index number to your preferred index:
DefaultTabController.of(context).animateTo(1);
You can use TabController:
TabController _controller = TabController(
vsync: this,
length: 3,
initialIndex: 0,
);
_controller.animateTo(_currentTabIndex);
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _controller,
tabs: [
...
],
),
),
body: TabBarView(
controller: _controller,
children: [
...
],
),
);
And than, setState to update screen:
int _currentTabIndex = 0;
setState(() {
_currentTabIndex = 1;
});
chemamolin's answer above is correct, but for additional clarification/tip, if you want to call your tabcontroller "from anywhere", also make sure the tabcontroller is not a private property of the class by removing the underscore, otherwise the distant class will not be able to see the tabcontroller with the example provided even when using the GlobalKey.
In other words, change
TabController _tabController;
to:
TabController tabController;
and change
MyApp._myTabbedPageKey.currentState._tabController.animateTo(...);
to:
MyApp._myTabbedPageKey.currentState.tabController.animateTo(...);
and everywhere else you reference tabcontroller.
If you want to jump to a specific page, you can use
PageController.jumpToPage(int)
However if you need animation, you'd use
PageController.animateToPage(page, duration: duration, curve: curve)
Simple example demonstrating it.
// create a PageController
final _controller = PageController();
bool _shouldAnimate = true; // whether we animate or jump
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_shouldAnimate) {
// animates to page1 with animation
_controller.animateToPage(1, duration: Duration(seconds: 1), curve: Curves.easeOut);
} else {
// jump to page1 without animation
_controller.jumpToPage(1);
}
},
),
body: PageView(
controller: _controller, // assign it to PageView
children: <Widget>[
FlutterLogo(colors: Colors.orange), // page0
FlutterLogo(colors: Colors.green), // page1
FlutterLogo(colors: Colors.red), // page2
],
),
);
}
DefaultTabController(
length: 4,
initialIndex: 0,
child: TabBar(
tabs: [
Tab(
child: Text(
"People",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"Events",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"Places",
style: TextStyle(
color: Colors.black,
),
),
),
Tab(
child: Text(
"HashTags",
style: TextStyle(
color: Colors.black,
),
),
),
],
),
)
i was trying to solve similar issue but passing methods or controllers down the widget tree wasn't a clean option for me. i had requirement to go back to tabbed page from other non-tabbed routes (back to specific tabs).
following solution worked for me
Inside tabbed page: read route arguments
#override
Widget build(BuildContext context) {
final String? tabId = Get.arguments;
_selectedTabIndex = tabId !=null? int.parse(tabId): 0;
return Scaffold(
....
body: _pages[_selectedPageIndex]['page'] as Widget,
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
....);
}
Now the calling page
onSubmit:() { // or some other event
// do something here
Get.offAndToNamed(Routes.homeTabs,
arguments: TabIndex.specialTab.index.toString());
//Routes is a const & TabIndex is enum defined somewhere
}
A solution with TabController + Streams
Pass a stream into the state object. Pass the new tab index through the stream for the state to update itself. Here's how I'm doing it.
import 'package:flutter/material.dart';
class TabsWidget extends StatefulWidget {
const TabsWidget({Key? key, this.tabs = const [], this.changeReceiver}) : super(key: key);
final List<Tab> tabs;
// To change the tab from outside, pass in the tab index through a stream
final Stream<int>? changeReceiver;
#override
State<TabsWidget> createState() => _TabsWidgetState();
}
class _TabsWidgetState extends State<TabsWidget> with SingleTickerProviderStateMixin {
int _index = 0;
late TabController _tabController;
#override
void initState() {
_tabController = TabController(length: widget.tabs.length, vsync: this, initialIndex: _index);
// Listen to tab index changes from external sources via this stream
widget.changeReceiver?.listen((int newIndex) {
setState(() {
_index = newIndex;
_tabController.animateTo(newIndex);
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
if (widget.tabs.isEmpty) return const SizedBox.shrink(); // If no tabs, show nothing
return TabBar(tabs: widget.tabs, controller: _tabController, );
}
}
// Sample usage - main
import 'dart:async';
import 'package:flutter/material.dart';
import 'tabs_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final StreamController<int> tabChangeNotifier = StreamController();
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Tab Change Demo',
home: Scaffold(
appBar: AppBar(
title: const Text('Tab Change Demo'),
),
body: SingleChildScrollView(child: Column(
children: [
const SizedBox(height: 30,),
Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
ElevatedButton(onPressed: () => tabChangeNotifier.add(0), child: const Text('Go Orange')),
ElevatedButton(onPressed: () => tabChangeNotifier.add(1), child: const Text('Go Red')),
ElevatedButton(onPressed: () => tabChangeNotifier.add(2), child: const Text('Go Green')),
],),
const SizedBox(height: 30,),
TabsWidget(changeReceiver: tabChangeNotifier.stream, tabs: const [
Tab(icon: Icon(Icons.circle, color: Colors.orange,),),
Tab(icon: Icon(Icons.circle, color: Colors.red,),),
Tab(icon: Icon(Icons.circle, color: Colors.green,),),
],),
],
),), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
#override
void dispose() {
tabChangeNotifier.close();
super.dispose();
}
}
This is how the above sample looks.
Use DefaultTabController instead of a local TabController, high enough in your widget tree, and then you'll have access to it from anywhere in that sub tree.
Widget build(BuildContext context) {
return DefaultTabController(
initialIndex: initialIndex,
length: tabs.length,
child: SizedBox( // From here down you have access to the tab controller
width: double.infinity,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SomeWidget(), // Has access to the controller
TabBar(
controller: DefaultTabController.of(context),
tabs:
tabs.map((tab) => Tab(child: Text(tab.title, style: const TextStyle(color: Colors.black)))).toList(),
),
Expanded(
child: TabBarView(
controller: DefaultTabController.of(context),
children: tabs.map((tab) => tab.widget).toList(),
),
),
],
),
),
);
}
In any point in that tree, you can access the tab controller with DefaultTabController.of(context) and change the tab, like so:
DefaultTabController.of(context)?.animateTo(0);
class Tab bar
class TabBarScreen extends StatefulWidget {
TabBarScreen({Key key}) : super(key: key);
#override
_TabBarScreenState createState() => _TabBarScreenState();
}
final List<Tab> tabs = <Tab>[
Tab(text: 'Page1'),
Tab(text: 'Page2'),
];
class _TabBarScreenState extends State<TabBarScreen> with SingleTickerProviderStateMixin {
TabController tabController;
#override
void initState() {
super.initState();
tabController = new TabController(vsync: this, length: tabs.length);
}
#override
void dispose() {
tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
backgroundColor: Theme.of(context).primaryColor,
appBar: AppBar(
backgroundColor: Theme.of(context).primaryColor,
centerTitle: true,
shape: Border(bottom: BorderSide(color: Colors.white)),
title: Text("Tab Bar",),
bottom: TabBar(
controller: tabController,
tabs: tabs,
indicatorWeight: 5,
indicatorColor: Colors.white,
labelColor: Colors.white,
),
),
body: TabBarView(
controller: tabController,
children: [
PageOneScreen(controller: tabController),
PageTwoScreen(controller: tabController),
],
),
),
);
}
}
class PageOne
class PageOneScreen extends StatefulWidget {
#override
_PageOneScreenState createState() => _PageOneScreenState();
PageOneScreen({controller}) {
tabController = controller;
}
}
TabController tabController;
class _PageOneScreenState extends State<PageOneScreen> {
#override
Widget build(BuildContext context) {
return Column(
children: [
RaisedButton(
onPressed: () {
tabController.animateTo(1); // number : index page
},
child: Text(
"Go To Page 2",
),
),
],
);
}
}
I would like to highlight the border of this card whenever is selected, so the user will see that specific card has been selected.
Try this !
The Result :
The Code :
import 'package:flutter/material.dart';
void main() => runApp(
new MaterialApp(
home: new MyApp(),
),
);
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("NonstopIO"),
),
body: new ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int index) {
return new MyCustomWidget(
title: "Title $index",
subtitle: "$index",
);
},
),
);
}
}
class MyCustomWidget extends StatefulWidget {
final String title;
final String subtitle;
const MyCustomWidget({Key key, this.title, this.subtitle}) : super(key: key);
#override
_MyCustomWidgetState createState() => _MyCustomWidgetState();
}
class _MyCustomWidgetState extends State<MyCustomWidget> {
bool selected = false;
#override
Widget build(BuildContext context) {
return new Card(
shape: selected
? new RoundedRectangleBorder(
side: new BorderSide(color: Colors.blue, width: 2.0),
borderRadius: BorderRadius.circular(4.0))
: new RoundedRectangleBorder(
side: new BorderSide(color: Colors.white, width: 2.0),
borderRadius: BorderRadius.circular(4.0)),
child: new Padding(
padding: const EdgeInsets.all(4.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Text(widget.title),
new Text(widget.subtitle),
new Checkbox(
value: selected,
onChanged: (value) {
setState(() {
selected = value;
});
})
],
),
),
);
}
}
I found something useful and similar to what I would like to achieve.
Flutter - I want to select the card by onLongPress?
I have a home page that uses Scaffold and a bottom navigation. In the body of the Scaffold, I have a page that contains 2 Card Widgets. I am trying to implement a "sub" tabBar navigation for the second card that is independent of the bottom navigation. Is there a way to implement tabbed bar navigation for a Card widget? I have tried using Scaffold but the tab bar height is rather too large. From the docs, this seems to be due to the flexiblespace Widget but I haven't found a way to eliminate/scale it, pointers will be appreciated.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new MyTabs()));
}
class MyTabs extends StatefulWidget {
#override
MyTabsState createState() => new MyTabsState();
}
class MyTabsState extends State<MyTabs> with SingleTickerProviderStateMixin {
TabController controller;
#override
void initState() {
super.initState();
controller = new TabController(vsync: this, length: 3);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Pages"),
backgroundColor: Colors.deepOrange,
),
bottomNavigationBar: new Material(
color: Colors.deepOrange,
child: new TabBar(controller: controller, tabs: <Tab>[
new Tab(icon: new Icon(Icons.arrow_forward)),
new Tab(icon: new Icon(Icons.arrow_downward)),
new Tab(icon: new Icon(Icons.arrow_back)),
])),
body: new TabBarView(controller: controller, children: <Widget>[
new First(),
new Second(),
new Third()
]));
}
}
class First extends StatefulWidget {
#override
FirstState createState() => new FirstState();
}
class FirstState extends State<First> with SingleTickerProviderStateMixin {
TabController controller1;
#override
void initState() {
super.initState();
controller1 = new TabController(vsync: this, length: 3);
}
#override
void dispose() {
controller1.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Column(children: [
new Expanded(
child: new Card(
child: new Center(
child: new Icon(Icons.favorite,
size: 150.0, color: Colors.redAccent))),
),
new Expanded(child: new Card(
child: new Center(
child: new Container(
child: new Scaffold(
appBar: new AppBar(
bottom: new TabBar(
controller: controller1,
tabs: <Tab>[
new Tab(icon: new Icon(Icons.arrow_forward)),
new Tab(icon: new Icon(Icons.arrow_downward)),
new Tab(icon: new Icon(Icons.arrow_back)),
]
)),
body: new TabBarView(
controller: controller1,
children: <Widget>[
new Text("Hello"),
new Text("world"),
new Text("Hi")
]
)
)
)
))),
]);
}
}
class Second extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container(
child: new Center(
child: new Icon(Icons.favorite, size: 150.0, color: Colors.redAccent)
)
);
}
}
class Third extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container(
child: new Center(
child: new Icon(Icons.favorite, size: 150.0, color: Colors.redAccent)
)
);
}
}
Code is modified from here
This code should do what you want. You don't need to use a nested Scaffold or AppBar, a simple Column is all you need.
As an aside, you might want to consider a BottomNavigationBar since TabBar is a Material widget that normally appears on top of the content it's navigating.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(home: new MyTabs()));
}
class MyTabs extends StatefulWidget {
#override
MyTabsState createState() => new MyTabsState();
}
class MyTabsState extends State<MyTabs> with SingleTickerProviderStateMixin {
TabController controller;
#override
void initState() {
super.initState();
controller = new TabController(vsync: this, length: 3);
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Pages"),
backgroundColor: Colors.deepOrange,
),
bottomNavigationBar: new Material(
color: Colors.deepOrange,
child: new TabBar(controller: controller, tabs: <Tab>[
new Tab(icon: new Icon(Icons.arrow_forward)),
new Tab(icon: new Icon(Icons.arrow_downward)),
new Tab(icon: new Icon(Icons.arrow_back)),
])),
body: new TabBarView(controller: controller, children: <Widget>[
new First(),
new Second(),
new Third()
]));
}
}
class First extends StatefulWidget {
#override
FirstState createState() => new FirstState();
}
class FirstState extends State<First> with SingleTickerProviderStateMixin {
TabController controller1;
#override
void initState() {
super.initState();
controller1 = new TabController(vsync: this, length: 3, initialIndex: 0);
}
#override
void dispose() {
controller1.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Column(children: [
new Expanded(
child: new Card(
child: new Center(
child: new Icon(Icons.favorite,
size: 150.0, color: Colors.redAccent))),
),
new Expanded(
child: new Card(
child: new Column(
children: <Widget>[
new Expanded(
child: new TabBarView(
controller: controller1,
children: <Widget>[
new Text("Hello"),
new Text("world"),
new Text("Hi")
]
),
),
new Container(
color: Colors.blue,
child: new TabBar(
controller: controller1,
tabs: <Tab>[
new Tab(icon: new Icon(Icons.arrow_forward)),
new Tab(icon: new Icon(Icons.arrow_downward)),
new Tab(icon: new Icon(Icons.arrow_back)),
]
),
),
],
),
)
),
]);
}
}
class Second extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container(
child: new Center(
child: new Icon(Icons.favorite, size: 150.0, color: Colors.redAccent)
)
);
}
}
class Third extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container(
child: new Center(
child: new Icon(Icons.favorite, size: 150.0, color: Colors.redAccent)
)
);
}
}