Flutter Customizing TabBar Indicator
6,272
Solution 1
You need to use AnimatedBuilder
to listen to the tab controller animation when tab controller animate from one index to another. Using this approach you can customize the transition animation of the TabBar
items. For example: Change your TabBar
implementation to the following
TabBar(
tabs: tabs
.asMap().entries
.map((entry) => AnimatedBuilder(
animation: _tabController.animation,
builder: (ctx, snapshot) {
final forward = _tabController.offset > 0;
final backward = _tabController.offset < 0;
int _fromIndex;
int _toIndex;
double progress;
// This value is true during the [animateTo] animation that's triggered when the user taps a [TabBar] tab.
// It is false when [offset] is changing as a consequence of the user dragging the [TabBarView].
if (_tabController.indexIsChanging) {
_fromIndex = _tabController.previousIndex;
_toIndex = _tabController.index;
_cachedFromIdx = _tabController.previousIndex;
_cachedToIdx = _tabController.index;
progress = (_tabController.animation.value - _fromIndex).abs() / (_toIndex - _fromIndex).abs();
} else {
if (_cachedFromIdx == _tabController.previousIndex && _cachedToIdx == _tabController.index) {
// When user tap on a tab bar and the animation is completed, it will execute this block
// This block will not be called when user draging the TabBarView
_fromIndex = _cachedFromIdx;
_toIndex = _cachedToIdx;
progress = 1;
_cachedToIdx = null;
_cachedFromIdx = null;
} else {
_cachedToIdx = null;
_cachedFromIdx = null;
_fromIndex = _tabController.index;
_toIndex = forward
? _fromIndex + 1
: backward
? _fromIndex - 1
: _fromIndex;
progress = (_tabController.animation.value - _fromIndex).abs();
}
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: entry.key == _fromIndex
? Color.lerp(Colors.white, Colors.red.shade900, progress)
: entry.key == _toIndex
? Color.lerp(Colors.red.shade900, Colors.white, progress)
: Color.lerp(Colors.red.shade900, Colors.red.shade900, progress),
borderRadius: BorderRadius.circular(200),
),
child: Text(
entry.value.toUpperCase(),
style: TextStyle(
fontSize: 10,
letterSpacing: 0.4,
fontWeight: FontWeight.w700,
),
),
);
},
))
.toList(),
controller: _tabController,
isScrollable: true,
indicatorSize: TabBarIndicatorSize.label,
indicatorWeight: 0,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(100),
),
physics: const ClampingScrollPhysics(),
unselectedLabelColor: Colors.white,
labelColor: Colors.red,
labelPadding: EdgeInsets.only(left: 12),
),
Notice in the example above, I use 2 local variables to keep track of current and previous indices. This is specifically to handle cases when user tabs on the TabBar
tab to animate between one index to another. Put log command in AnimatedBuilder
's builder method to better understand of how it works.
int _cachedFromIdx;
int _cachedToIdx;
Here's the result
Solution 2
Check this code
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
bottom: PreferredSize(
preferredSize: Size(100,20),
child: Column(
children: [
TabBar(
// indicatorSize: TabBarIndicatorSize.tab,
indicatorColor: Colors.transparent,
labelColor: Colors.white,
unselectedLabelColor: Colors.black,
indicator: BoxDecoration(
borderRadius: BorderRadius.circular(50),
color: Colors.redAccent),
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.report_problem)),
Tab(icon: Icon(Icons.report_problem)),
],
),
SizedBox(height: 10,)
],
),
),
),
body: TabBarView(
children: [
Icon(Icons.directions_car),
Icon(Icons.directions_transit),
Icon(Icons.directions_bike),
],
)
),
);
}
}
Author by
Jihad Naji
Updated on December 24, 2022Comments
-
Jihad Naji over 1 year
How can I customize the Tab indicator in the Flutter TabBar to achieve the target result below?
How can I do the following:
- Change unselected tabs as in the target picture
- Adjust the ripple borders to fit the content and have rounded edges.
This is my code
class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { var categoryTabs = <Tab>[...]; @override Widget build(BuildContext context) { return DefaultTabController( length: categoryTabs.length, child: Scaffold( appBar: AppBar( title: Text('My App'), centerTitle: true, bottom: PreferredSize( preferredSize: Size(100, 70), child: Column( children: [ TabBar( indicatorSize: TabBarIndicatorSize.tab, indicatorColor: Colors.transparent, labelColor: colorPrimaryDark, isScrollable: true, unselectedLabelColor: Colors.white, indicator: BoxDecoration( borderRadius: BorderRadius.circular(50), color: Colors.white, ), tabs: categoryTabs, ), SizedBox(height: 10) ], ), ), ), body: SafeArea(...), ), ); } }
-
Jihad Naji over 3 yearsThank you @mr-vd, could you please recheck the question, I updated it.