How to create a dynamic TabBarView/ Render a new Tab with a function in Flutter?
Solution 1
To make dynamic tab you can use a List and keep appending the list on every button click.
Trick: Clear List and redraw an empty widget and again draw the widgets as per your list.
import 'package:flutter/material.dart';
void main() {
runApp(new MaterialApp(
home: new CardStack(),
));
}
class DynamicTabContent {
IconData icon;
String tooTip;
DynamicTabContent.name(this.icon, this.tooTip);
}
class CardStack extends StatefulWidget {
@override
_MainState createState() => new _MainState();
}
class _MainState extends State<CardStack> with TickerProviderStateMixin {
List<DynamicTabContent> myList = new List();
TabController _cardController;
TabPageSelector _tabPageSelector;
@override
void initState() {
super.initState();
myList.add(new DynamicTabContent.name(Icons.favorite, "Favorited"));
myList.add(new DynamicTabContent.name(Icons.local_pizza, "local pizza"));
_cardController = new TabController(vsync: this, length: myList.length);
_tabPageSelector = new TabPageSelector(controller: _cardController);
}
@override
void dispose() {
_cardController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.grey[300],
appBar: new AppBar(
actions: <Widget>[
new Padding(
padding: const EdgeInsets.all(1.0),
child: new IconButton(
icon: const Icon(
Icons.add,
size: 30.0,
color: Colors.white,
),
tooltip: 'Add Tabs',
onPressed: () {
List<DynamicTabContent> tempList = new List();
myList.forEach((dynamicContent) {
tempList.add(dynamicContent);
});
setState(() {
myList.clear();
});
if (tempList.length % 2 == 0) {
myList.add(new DynamicTabContent.name(Icons.shopping_cart, "shopping cart"));
} else {
myList.add(new DynamicTabContent.name(Icons.camera, "camera"));
}
tempList.forEach((dynamicContent) {
myList.add(dynamicContent);
});
setState(() {
_cardController = new TabController(vsync: this, length: myList.length);
_tabPageSelector = new TabPageSelector(controller: _cardController);
});
},
),
),
],
title: new Text("Title Here"),
bottom: new PreferredSize(
preferredSize: const Size.fromHeight(10.0),
child: new Theme(
data: Theme.of(context).copyWith(accentColor: Colors.grey),
child: myList.isEmpty
? new Container(
height: 30.0,
)
: new Container(
height: 30.0,
alignment: Alignment.center,
child: _tabPageSelector,
),
))),
body: new TabBarView(
controller: _cardController,
children: myList.isEmpty
? <Widget>[]
: myList.map((dynamicContent) {
return new Card(
child: new Container(
height: 450.0,
width: 300.0,
child: new IconButton(
icon: new Icon(dynamicContent.icon, size: 100.0),
tooltip: dynamicContent.tooTip,
onPressed: null,
)),
);
}).toList(),
),
);
}
}
Hope this helps :)
Solution 2
Problems arise if you need to modify the arrays. They consist in the fact that when modifying an array you do not have the opportunity to use the same controller.
You can use the next custom widget for this case:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> data = ['Page 0', 'Page 1', 'Page 2'];
int initPosition = 1;
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: CustomTabView(
initPosition: initPosition,
itemCount: data.length,
tabBuilder: (context, index) => Tab(text: data[index]),
pageBuilder: (context, index) => Center(child: Text(data[index])),
onPositionChange: (index){
print('current position: $index');
initPosition = index;
},
onScroll: (position) => print('$position'),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
data.add('Page ${data.length}');
});
},
child: Icon(Icons.add),
),
);
}
}
/// Implementation
class CustomTabView extends StatefulWidget {
final int itemCount;
final IndexedWidgetBuilder tabBuilder;
final IndexedWidgetBuilder pageBuilder;
final Widget stub;
final ValueChanged<int> onPositionChange;
final ValueChanged<double> onScroll;
final int initPosition;
CustomTabView({
@required this.itemCount,
@required this.tabBuilder,
@required this.pageBuilder,
this.stub,
this.onPositionChange,
this.onScroll,
this.initPosition,
});
@override
_CustomTabsState createState() => _CustomTabsState();
}
class _CustomTabsState extends State<CustomTabView> with TickerProviderStateMixin {
TabController controller;
int _currentCount;
int _currentPosition;
@override
void initState() {
_currentPosition = widget.initPosition ?? 0;
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
_currentCount = widget.itemCount;
super.initState();
}
@override
void didUpdateWidget(CustomTabView oldWidget) {
if (_currentCount != widget.itemCount) {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
if (widget.initPosition != null) {
_currentPosition = widget.initPosition;
}
if (_currentPosition > widget.itemCount - 1) {
_currentPosition = widget.itemCount - 1;
_currentPosition = _currentPosition < 0 ? 0 :
_currentPosition;
if (widget.onPositionChange is ValueChanged<int>) {
WidgetsBinding.instance.addPostFrameCallback((_){
if(mounted) {
widget.onPositionChange(_currentPosition);
}
});
}
}
_currentCount = widget.itemCount;
setState(() {
controller = TabController(
length: widget.itemCount,
vsync: this,
initialIndex: _currentPosition,
);
controller.addListener(onPositionChange);
controller.animation.addListener(onScroll);
});
} else if (widget.initPosition != null) {
controller.animateTo(widget.initPosition);
}
super.didUpdateWidget(oldWidget);
}
@override
void dispose() {
controller.animation.removeListener(onScroll);
controller.removeListener(onPositionChange);
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.itemCount < 1) return widget.stub ?? Container();
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Container(
alignment: Alignment.center,
child: TabBar(
isScrollable: true,
controller: controller,
labelColor: Theme.of(context).primaryColor,
unselectedLabelColor: Theme.of(context).hintColor,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).primaryColor,
width: 2,
),
),
),
tabs: List.generate(
widget.itemCount,
(index) => widget.tabBuilder(context, index),
),
),
),
Expanded(
child: TabBarView(
controller: controller,
children: List.generate(
widget.itemCount,
(index) => widget.pageBuilder(context, index),
),
),
),
],
);
}
onPositionChange() {
if (!controller.indexIsChanging) {
_currentPosition = controller.index;
if (widget.onPositionChange is ValueChanged<int>) {
widget.onPositionChange(_currentPosition);
}
}
}
onScroll() {
if (widget.onScroll is ValueChanged<double>) {
widget.onScroll(controller.animation.value);
}
}
}
Andy Giovanny Febrianto
Updated on July 09, 2022Comments
-
Andy Giovanny Febrianto almost 2 years
So I have been learning flutter in a while and I am stuck in this. Sorry if it is a noobish question. I am currently trying to build something like a Card Tab. The information and widget will be stored in a card.
Imagine something like Tinder, where they have multiple card stack and swipe left and right to navigate.
I plan to create that but I cannot seems to find a way to add/render a new card with a button.
It's like adding something to the list, Flutter will use a ListView builder where we add to the list. But there is no TabBarView builder. Is this something that is not possible to do? I try putting a list inside a tab but it's still wont be the same.
I created some basic skeleton here to help convey my meaning. So the card will be swipe left and right and there is a button in the appBar to add card. Lenght is 2 now and I wanted the button to render the 3rd card. Is this possible?
Thanks in advance!
import 'package:flutter/material.dart'; void main() { runApp(new MaterialApp( home: new CardStack(), )); } class CardStack extends StatefulWidget { @override _MainState createState() => new _MainState(); } class _MainState extends State<CardStack> with SingleTickerProviderStateMixin { TabController _cardController; @override void initState() { super.initState(); _cardController = new TabController(vsync: this, length: 2); } @override void dispose() { _cardController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return new Scaffold( backgroundColor: Colors.grey[300], appBar: new AppBar( actions: <Widget>[ new IconButton( icon: const Icon(Icons.add), tooltip: 'Add Tabs', onPressed: null, ), ], title: new Text("Title Here"), bottom: new PreferredSize( preferredSize: const Size.fromHeight(20.0), child: new Theme( data: Theme.of(context).copyWith(accentColor: Colors.grey), child: new Container( height: 50.0, alignment: Alignment.center, child: new TabPageSelector(controller: _cardController), ), ) ) ), body: new TabBarView( controller: _cardController, children: <Widget>[ new Center( child: new Card( child: new Container( height: 450.0, width: 300.0, child: new IconButton( icon: new Icon(Icons.favorite, size: 100.0), tooltip: 'Favorited', onPressed: null, ) ), ), ), new Center( child: new Card( child: new Container( height: 450.0, width: 300.0, child: new IconButton( icon: new Icon(Icons.local_pizza, size: 50.0,), tooltip: 'Pizza', onPressed: null, ) ), ), ), ], ), ); } }
-
rajeshzmoke over 5 yearsi ran this example..and i couldnt get the index of the current tab..as you are using a map to display all the tabs, i need the index to show certain components and hide some
-
Telmo Marques over 5 yearsThere's an issue with this solution, where
TabPageSelector
loses it's state when a newTabController
is instantiated insidebuild()
. I'm also still trying to fix this issue. Please see: webmshare.com/ZQ00d -
Amalan Dhananjayan about 4 yearsThis is very helpful. Thanks (y)
-
VenSan almost 4 years@Yuriy Luchaninov, If I want to add the "delete page" option for tab view based on selection respective page should remove from the PageBuilder.
-
VenSan almost 4 yearsPlease find the tab delete image link reference. ibb.co/9NPNkkZ
-
Abbas Jafari over 3 yearsExcellent friend
-
Sebastian Gomes over 3 yearsThis is gold. Much appreciated.
-
Jani about 3 yearsThis should be a package
-
Emon almost 3 yearsIt should be accepted answer and Thanks @Yuriy Luchaninov . Works like a charm.
-
Apoorv Pandey almost 3 yearsSuper amazing, thanks a lot this should be accepted as answer