Building a TabBar Widget with Badges in Flutter
Flutter's TabBar widget is a fundamental component for navigation within applications, allowing users to switch between different views easily. While the default Tab widget provides basic icon and text options, many modern applications require visual indicators like badges to highlight unread messages, pending notifications, or new content. This article will guide you through building a custom TabBar with badges, enhancing user experience and providing crucial visual cues.
Understanding the Basic TabBar
Before diving into badges, let's briefly review the standard TabBar implementation. It typically works in conjunction with a TabController and a TabBarView to synchronize the tab selection with the displayed content.
Here's a basic structure:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: DefaultTabController(
length: 3, // Number of tabs
child: Scaffold(
appBar: AppBar(
title: Text('Basic TabBar'),
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.home), text: 'Home'),
Tab(icon: Icon(Icons.notifications), text: 'Notifications'),
Tab(icon: Icon(Icons.settings), text: 'Settings'),
],
),
),
body: TabBarView(
children: [
Center(child: Text('Home Content')),
Center(child: Text('Notifications Content')),
Center(child: Text('Settings Content')),
],
),
),
),
);
}
}
In this setup, each Tab widget receives an Icon and Text. To add a badge, we need to create a custom widget that combines these elements with a badge indicator.
Creating a Custom Tab Widget with Badge
The key to adding badges is to create a custom widget that will serve as the child of each Tab. This custom widget will use a Stack to overlay a small badge container on top of the tab's icon and text.
We'll define a CustomTabWithBadge widget. This widget takes an icon, text, and an optional badgeCount. If badgeCount is greater than 0, a red circular badge displaying the count will be shown.
import 'package:flutter/material.dart';
class CustomTabWithBadge extends StatelessWidget {
final IconData icon;
final String text;
final int badgeCount;
const CustomTabWithBadge({
Key? key,
required this.icon,
required this.text,
this.badgeCount = 0,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Tab(
child: Stack(
// Allow the badge to overflow the Stack's bounds, so it can sit at the corner of the tab.
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
// The main content of the tab (icon and text)
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon),
Text(text),
],
),
// The badge widget, conditionally displayed
if (badgeCount > 0)
Positioned(
right: -5, // Adjust positioning as needed to place it effectively
top: -5, // Adjust positioning as needed
child: Container(
padding: EdgeInsets.all(2.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.red,
),
constraints: BoxConstraints(
minWidth: 16,
minHeight: 16,
),
child: Text(
badgeCount.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
),
),
),
],
),
);
}
}
Key aspects of the CustomTabWithBadge widget:
- It returns a
Tabwidget, making it compatible withTabBar.tabs. - Inside the
Tab, aStackis used to layer widgets. clipBehavior: Clip.noneis crucial. It allows the badge (which is aPositionedwidget) to extend beyond the nominal bounds of theStack, making it appear at the corner of the tab itself, rather than being confined within the icon/text column.- The
Positionedwidget'srightandtopproperties are adjusted to place the badge precisely at the desired corner. These values might need fine-tuning based on your design. - The badge itself is a small
Containerwith rounded corners, containing aTextwidget for the count.
Integrating Badges into a Stateful TabBar
To make the badges dynamic (e.g., updating counts), we'll use a StatefulWidget and manage the badge counts within its state. We'll also use a TabController for more control over tab selection.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
// CustomTabWithBadge widget as defined above
class CustomTabWithBadge extends StatelessWidget {
final IconData icon;
final String text;
final int badgeCount;
const CustomTabWithBadge({
Key? key,
required this.icon,
required this.text,
this.badgeCount = 0,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Tab(
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(icon),
Text(text),
],
),
if (badgeCount > 0)
Positioned(
right: -5,
top: -5,
child: Container(
padding: EdgeInsets.all(2.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
color: Colors.red,
),
constraints: BoxConstraints(
minWidth: 16,
minHeight: 16,
),
child: Text(
badgeCount.toString(),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10,
color: Colors.white,
),
),
),
),
],
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TabBar with Badge Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: TabBarBadgeScreen(),
);
}
}
class TabBarBadgeScreen extends StatefulWidget {
@override
_TabBarBadgeScreenState createState() => _TabBarBadgeScreenState();
}
class _TabBarBadgeScreenState extends State with SingleTickerProviderStateMixin {
late TabController _tabController;
int _notificationBadgeCount = 5; // Initial badge count for notifications
int _messageBadgeCount = 2; // Initial badge count for messages
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
void _incrementNotificationBadge() {
setState(() {
_notificationBadgeCount++;
});
}
void _resetBadges() {
setState(() {
_notificationBadgeCount = 0;
_messageBadgeCount = 0;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TabBar with Badges'),
bottom: TabBar(
controller: _tabController,
tabs: [
CustomTabWithBadge(icon: Icons.home, text: 'Home'),
CustomTabWithBadge(icon: Icons.notifications, text: 'Notifications', badgeCount: _notificationBadgeCount),
CustomTabWithBadge(icon: Icons.message, text: 'Messages', badgeCount: _messageBadgeCount),
],
),
),
body: TabBarView(
controller: _tabController,
children: [
Center(child: Text('Home Content')),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Notifications Content'),
ElevatedButton(
onPressed: _incrementNotificationBadge,
child: Text('Add Notification'),
),
ElevatedButton(
onPressed: _resetBadges,
child: Text('Reset All Badges'),
),
],
),
),
Center(child: Text('Messages Content')),
],
),
// Example of programmatic tab control
floatingActionButton: FloatingActionButton(
onPressed: () {
// Programmatically switch to the notifications tab (index 1)
_tabController.animateTo(1);
},
child: Icon(Icons.send),
),
);
}
}
In this complete example:
- The
TabBarBadgeScreenis aStatefulWidgetto hold the badge counts in its state. SingleTickerProviderStateMixinis used to provide thevsyncproperty required byTabController._notificationBadgeCountand_messageBadgeCountare state variables that control the badge visibility and number.- The
TabBar'stabslist now uses ourCustomTabWithBadge, passing the current badge counts. - Buttons are added to demonstrate how you can increment or reset badge counts, triggering a
setStatecall to rebuild the UI and update the badges. - A
FloatingActionButtondemonstrates programmatically switching tabs using_tabController.animateTo().
Conclusion
By leveraging Flutter's powerful composition model, building custom widgets like a TabBar with badges is straightforward. This approach not only allows you to integrate dynamic visual indicators for notifications or unread counts but also opens the door to further customization of your tabs. This enhanced TabBar improves the clarity and interactivity of your application, providing a more intuitive and informative user experience.