Related
is it possible to use OnWillpopScope to be like the twitter app when your in a different tab when you press back it returns home or the tabindex gets or sets to zero.
Yes it can be done using TabController. It has a method animateTo() which can be used in the onWillPop callback of the WillPopScope widget.
Here is an example implementation
class HomePage extends StatefulWidget {
#override
HomePageState createState() {
return new HomePageState();
}
}
class HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
List<Widget> _tabs = <Widget>[
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
];
TabController _tabController;
initState() {
super.initState();
_tabController = TabController(length: _tabs.length, vsync: this);
}
#override
dispose(){
_tabController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
_tabController.animateTo(0);
},
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _tabController,
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
),
title: Text('Tabs Demo'),
),
body: TabBarView(
controller: _tabController,
children: _tabs.toList(),
),
),
);
}
}
I have this:
final GlobalKey<ScaffoldState> _scaffoldkey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: _scaffoldkey,
drawer: Menu(),
appBar: AppBar(
title: Container(
child: Text('Dashboard'),
),
bottom: TabBar(
tabs: <Widget>[
...
],
),
),
body: TabBarView(
children: <Widget>[
...
],
),
),
);
}
}
Now, the drawer: Menu() is imported from another menu.dart file, which looks like this:
class Menu extends StatelessWidget {
final GlobalKey<ScaffoldState> drawerKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return new Drawer(
key: drawerKey,
child: new ListView(
children: <Widget>[
new ListTile(
dense: true,
title: new Text('My Text'),
onTap: () {
// On tap this, I want to show a snackbar.
scaffoldKey.currentState.showSnackBar(showSnack('Error. Could not log out'));
},
),
],
),
);
}
}
With the above approach, I get
NoSuchMethodError: The method 'showSnackBar' was called on null.
An easy solution is to tuck the entire menu.dart contents in the drawer: ... directly.
Another way I'm looking at is being able to reference the parent scaffold in order to display the snackbar.
How can one achieve that?
Why can't one even just call the snackbar from anywhere in Flutter and compulsorily it has to be done via the Scaffold? Just why?
You should try to avoid using GlobalKey as much as possible; you're almost always better off using Scaffold.of to get the ScaffoldState. Since your menu is below the scaffold in the widget tree, Scaffold.of(context) will do what you want.
The reason what you're attempting to do doesn't work is that you are creating two seperate GlobalKeys - each of which is its own object. Think of them as global pointers - since you're creating two different ones, they point to different things. And the state should really be failing analysis since you're passing the wrong type into your Drawer's key field...
If you absolutely have to use GlobalKeys for some reason, you would be better off passing the instance created in your outer widget into your Menu class as a member i.e. this.scaffoldKey, but this isn't recommended.
Using Scaffold.of, this is what your code would look like in the onTap function:
onTap: () {
// On tap this, I want to show a snackbar.
Scaffold.of(context).showSnackBar(showSnack('Error. Could not log out'));
},
You can achieve this functionality by using builder widget you don't need to make separate GlobalKey or pass key as a parameter. Just wrap a widget to Builder widget
class CustomDrawer extends StatelessWidget {#override Widget build(BuildContext context) {
return new Drawer(
child: new ListView(
children: <Widget>[
new Builder(builder: (BuildContext innerContext) {
return ListTile(
dense: true,
title: new Text('My Text'),
onTap: () {
Navigator.of(context).pop();
Scaffold.of(innerContext).showSnackBar(SnackBar(
content: Text('Added added into cart'),
duration: Duration(seconds: 2),
action: SnackBarAction(label: 'UNDO', onPressed: () {}),
));
}
);
})
],
),
);}}
From your first question
In other to reference the parent scaffold in the menu widget you can pass the _scaffoldkey to the menu widget as parameter and use ScaffoldMessenger.of() to show snackbar as shown below
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// Root Widget
#override
Widget build(BuildContext context) {
return MaterialApp(
// App name
title: 'Flutter SnackBar',
// Theme
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Test(title: 'SnackBar'),
);
}
}
class Test extends StatefulWidget {
final String? title;
final GlobalKey<ScaffoldState> _scaffoldkey = new GlobalKey<ScaffoldState>();
Test({#required this.title});
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
key: widget._scaffoldkey,
drawer: Menu(parentScaffoldkey:widget._scaffoldkey),
appBar: AppBar(
title: Container(
child: Text('Dashboard'),
),
bottom: TabBar(
tabs: <Widget>[
Tab(text:"Home"),
Tab(text:"About")
],
),
),
body: TabBarView(
children: <Widget>[
Text("Home"),
Text("About")
],
),
),
);
}
}
Menu part as shown
class Menu extends StatelessWidget {
final parentScaffoldkey;
Menu({this.parentScaffoldkey});
#override
Widget build(BuildContext context) {
return new Drawer(
child: new ListView(
children: <Widget>[
new ListTile(
dense: true,
title: new Text('My Text'),
onTap: () {
// On tap show a snackbar.
// ScaffoldMessenger will call the nearest Scaffold to show snackbar
ScaffoldMessenger.of(this.parentScaffoldkey.currentContext).showSnackBar(SnackBar(content:Text('Error. Could not log out')));
},
),
],
),
);
}
}
Also,you have to call snackbar via Scaffold because it provides the SnackBar API and manages it
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 am learning flutter and I am working with tabBars and I am having an issue with saving the state. I have put a small working example of my issue below. Basically, there is a button and a stateful counter. When I click the button, I see the text field update correctly. But, when I switch to a different tab and come back, the text field is back to zero.
I have found if i move the following line outside of _CounterState so its defined at the top level of the file, then, it works correctly. When I switch tabs, the counter stays at the correct count when I switch back
int _counter = 0;
I don't feel like this is the appropriate way to do this and all of the examples I have seen have the variable inside of the class. Can anyone give me any insights? Why would it reset if it is inside the class? Am I supposed to keep it outside the class? Below is the simplified full example.
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 Counter(),
new Icon(Icons.directions_transit),
new Icon(Icons.directions_bike),
],
),
),
),
);
}
}
class Counter extends StatefulWidget {
#override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _increment() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new RaisedButton(
onPressed: _increment,
child: new Text('Increment'),
),
new Text('Count: $_counter'),
],
);
}
}
Below is the example with the counter moved outside of the class
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 Counter(),
new Icon(Icons.directions_transit),
new Icon(Icons.directions_bike),
],
),
),
),
);
}
}
class Counter extends StatefulWidget {
#override
_CounterState createState() => new _CounterState();
}
int _counter = 0; //<-- MOVED OUTSIDE THE _CounterState CLASS
class _CounterState extends State<Counter> {
void _increment() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new RaisedButton(
onPressed: _increment,
child: new Text('Increment'),
),
new Text('Count: $_counter'),
],
);
}
}
As _CounterState widget is built everytime you go to the given TabView you'll need to put _counter variable in the state configuration class (Counter).
class Counter extends StatefulWidget {
int _counter = 0;
#override
_CounterState createState() => new _CounterState();
}
class _CounterState extends State<Counter> {
void _increment() {
setState(() {
widget._counter++;
});
}
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new RaisedButton(
onPressed: _increment,
child: new Text('Increment'),
),
new Text('Count: ${widget._counter}'),
],
);
}
}
As I used one solution AutomaticKeepAliveClientMixin
You need to use this mixin with your state class of StateFullWidget.
you need to pass true to wantKeepAlive getter method.
class SampleWidget extends StatefulWidget {
#override
_SampleWidgetState createState() => _SampleWidgetState();
}
class _SampleWidgetState extends State<SampleWidget> with AutomaticKeepAliveClientMixin{
#override
Widget build(BuildContext context) {
super.build(context);
return Container();
}
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => true;
}
This will save your state and stop your widget to recreate again. I have used it with Tabbar and PageView and it's working fine.
put the variable in that statefulwidget and then call it every time as "widget.variable_name"
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)
)
);
}
}