Tab bars are an essential component of many mobile applications, providing a clear and intuitive way to navigate between different sections. Flutter offers a default TabBar
widget, but sometimes you need a custom design that aligns with your app’s unique branding and user experience goals. This blog post will guide you through the process of implementing custom tab bars in Flutter.
Why Use Custom Tab Bars?
- Branding: Align the tab bar’s appearance with your app’s unique branding.
- Improved User Experience: Enhance usability with custom animations and interactions.
- Unique Features: Incorporate features beyond basic tab switching, such as animated indicators or custom icons.
Implementing Custom Tab Bars in Flutter
To create a custom tab bar, you’ll typically use a combination of Flutter’s built-in widgets, such as Row
, GestureDetector
, and AnimatedContainer
.
Step 1: Set Up the Basic Structure
Start by creating a basic structure using a Row
to hold the tab items and a GestureDetector
to handle tap events.
import 'package:flutter/material.dart';
class CustomTabBar extends StatefulWidget {
final List tabs;
final Function(int) onTabSelected;
const CustomTabBar({Key? key, required this.tabs, required this.onTabSelected}) : super(key: key);
@override
_CustomTabBarState createState() => _CustomTabBarState();
}
class _CustomTabBarState extends State {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(widget.tabs.length, (index) {
return GestureDetector(
onTap: () {
setState(() {
_selectedIndex = index;
widget.onTabSelected(index);
});
},
child: Text(widget.tabs[index]),
);
}),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Custom Tab Bar Example')),
body: Column(
children: [
CustomTabBar(
tabs: const ['Home', 'Search', 'Profile'],
onTabSelected: (index) {
print('Selected Tab Index: $index');
},
),
const Expanded(
child: Center(
child: Text('Content for Selected Tab'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: HomeScreen()));
}
Step 2: Style the Tab Items
Add styling to make the tab items visually appealing and interactive.
import 'package:flutter/material.dart';
class CustomTabBar extends StatefulWidget {
final List tabs;
final Function(int) onTabSelected;
const CustomTabBar({Key? key, required this.tabs, required this.onTabSelected}) : super(key: key);
@override
_CustomTabBarState createState() => _CustomTabBarState();
}
class _CustomTabBarState extends State {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(widget.tabs.length, (index) {
return GestureDetector(
onTap: () {
setState(() {
_selectedIndex = index;
widget.onTabSelected(index);
});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
decoration: BoxDecoration(
color: _selectedIndex == index ? Colors.blue : Colors.transparent,
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
widget.tabs[index],
style: TextStyle(
color: _selectedIndex == index ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
),
),
),
);
}),
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Custom Tab Bar Example')),
body: Column(
children: [
CustomTabBar(
tabs: const ['Home', 'Search', 'Profile'],
onTabSelected: (index) {
print('Selected Tab Index: $index');
},
),
const Expanded(
child: Center(
child: Text('Content for Selected Tab'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: HomeScreen()));
}
Step 3: Add an Animated Indicator
Enhance the visual feedback by adding an animated indicator to highlight the selected tab.
import 'package:flutter/material.dart';
class CustomTabBar extends StatefulWidget {
final List tabs;
final Function(int) onTabSelected;
const CustomTabBar({Key? key, required this.tabs, required this.onTabSelected}) : super(key: key);
@override
_CustomTabBarState createState() => _CustomTabBarState();
}
class _CustomTabBarState extends State {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8.0),
),
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(widget.tabs.length, (index) {
return GestureDetector(
onTap: () {
setState(() {
_selectedIndex = index;
widget.onTabSelected(index);
});
},
child: Container(
padding: const EdgeInsets.symmetric(vertical: 12.0, horizontal: 24.0),
child: Text(
widget.tabs[index],
style: TextStyle(
color: _selectedIndex == index ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
),
),
),
);
}),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
left: _selectedIndex * (MediaQuery.of(context).size.width / widget.tabs.length),
top: 0,
bottom: 0,
child: Container(
width: MediaQuery.of(context).size.width / widget.tabs.length,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8.0),
),
),
),
],
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Custom Tab Bar Example')),
body: Column(
children: [
CustomTabBar(
tabs: const ['Home', 'Search', 'Profile'],
onTabSelected: (index) {
print('Selected Tab Index: $index');
},
),
const Expanded(
child: Center(
child: Text('Content for Selected Tab'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: HomeScreen()));
}
Step 4: Customize with Icons
Incorporate custom icons to make the tab bar more visually informative.
import 'package:flutter/material.dart';
class CustomTabBar extends StatefulWidget {
final List tabs;
final List icons; // List of icons
final Function(int) onTabSelected;
const CustomTabBar({Key? key, required this.tabs, required this.icons, required this.onTabSelected}) : super(key: key);
@override
_CustomTabBarState createState() => _CustomTabBarState();
}
class _CustomTabBarState extends State {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(8.0),
),
child: Stack(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: List.generate(widget.tabs.length, (index) {
return GestureDetector(
onTap: () {
setState(() {
_selectedIndex = index;
widget.onTabSelected(index);
});
},
child: Column(
children: [
Icon(
widget.icons[index], // Display corresponding icon
color: _selectedIndex == index ? Colors.white : Colors.black,
),
Text(
widget.tabs[index],
style: TextStyle(
color: _selectedIndex == index ? Colors.white : Colors.black,
fontWeight: FontWeight.bold,
),
),
],
),
);
}),
),
AnimatedPositioned(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
left: _selectedIndex * (MediaQuery.of(context).size.width / widget.tabs.length),
top: 0,
bottom: 0,
child: Container(
width: MediaQuery.of(context).size.width / widget.tabs.length,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8.0),
),
),
),
],
),
);
}
}
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Custom Tab Bar Example')),
body: Column(
children: [
CustomTabBar(
tabs: const ['Home', 'Search', 'Profile'],
icons: const [Icons.home, Icons.search, Icons.person], // List of icons
onTabSelected: (index) {
print('Selected Tab Index: $index');
},
),
const Expanded(
child: Center(
child: Text('Content for Selected Tab'),
),
),
],
),
);
}
}
void main() {
runApp(MaterialApp(home: HomeScreen()));
}
Conclusion
Implementing custom tab bars in Flutter provides a way to create visually appealing and user-friendly navigation. By combining Flutter’s widgets and animation capabilities, you can craft a unique tab bar that perfectly complements your app’s branding and enhances the user experience.