Flutter Sidebar With Animated Indicator Using Vertical Tabs

Kamlesh Paikrao
4 min readDec 2, 2020

--

Lets Get Started:

  1. Create File verticaltabs.dart
  2. Paste Following Code
import 'package:flutter/material.dart';/// A vertical tab widget for flutter
class VerticalTabs extends StatefulWidget {
final Key key;
final double tabsWidth;
final double itemExtent;
final double indicatorWidth;
final List<Tab> tabs;
final List<Widget> contents;
final TextDirection direction;
final Color indicatorColor;
final bool disabledChangePageFromContentView;
final Axis contentScrollAxis;
final Color selectedTabBackgroundColor;
final Color unselectedTabBackgroundColor;
final Color dividerColor;
final Duration changePageDuration;
final Curve changePageCurve;
final Color tabsShadowColor;
final double tabsElevation;
VerticalTabs(
{this.key,
@required this.tabs,
@required this.contents,
this.tabsWidth = 200,
this.itemExtent = 50,
this.indicatorWidth = 5,
this.direction = TextDirection.ltr,
this.indicatorColor = Colors.blueGrey,
this.disabledChangePageFromContentView = false,
this.contentScrollAxis = Axis.horizontal,
this.selectedTabBackgroundColor = Colors.white,
this.unselectedTabBackgroundColor = const Color(0xfff8f8f8),
this.dividerColor = const Color(0xffe5e5e5),
this.changePageCurve = Curves.easeInOut,
this.changePageDuration = const Duration(milliseconds: 300),
this.tabsShadowColor = Colors.black54,
this.tabsElevation = 2.0})
: assert(
tabs != null && contents != null && tabs.length == contents.length),
super(key: key);
@override
_VerticalTabsState createState() => _VerticalTabsState();
}
class _VerticalTabsState extends State<VerticalTabs>
with TickerProviderStateMixin {
int _selectedIndex = 0;
bool _changePageByTapView;
AnimationController animationController;
Animation<double> animation;
Animation<RelativeRect> rectAnimation;
PageController pageController = PageController(); List<AnimationController> animationControllers = []; ScrollPhysics pageScrollPhysics = AlwaysScrollableScrollPhysics(); @override
void initState() {
for (int i = 0; i < widget.tabs.length; i++) {
animationControllers.add(AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
));
}
_selectTab(0);
if (widget.disabledChangePageFromContentView == true)
pageScrollPhysics = NeverScrollableScrollPhysics();
super.initState();
}
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: widget.direction,
child: Column(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Material(
child: Container(
width: 200,
child: ListView.builder(
itemExtent: widget.itemExtent,
itemCount: widget.tabs.length,
itemBuilder: (context, index) {
Tab tab = widget.tabs[index];
Alignment alignment = Alignment.centerLeft;
if (widget.direction == TextDirection.rtl) {
alignment = Alignment.centerRight;
}
Widget child;
if (tab.child != null) {
child = tab.child;
} else {
child = Row(
children: <Widget>[
(tab.icon != null)
? Row(
children: <Widget>[
tab.icon,
SizedBox(
width: 5,
)
],
)
: Container(),
(tab.text != null) ? Text(tab.text) : Container(),
],
);
}
Color itemBGColor = widget.unselectedTabBackgroundColor;
if (_selectedIndex == index)
itemBGColor = widget.selectedTabBackgroundColor;
return GestureDetector(
onTap: () {
_changePageByTapView = true;
setState(() {
_selectTab(index);
});
pageController.animateToPage(index,
duration: widget.changePageDuration,
curve: widget.changePageCurve);
},
child: Container(
decoration: BoxDecoration(
color: itemBGColor,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ScaleTransition(
child: Container(
width: widget.indicatorWidth,
height: widget.itemExtent,
color: widget.indicatorColor,
),
scale: Tween(begin: 0.0, end: 1.0).animate(
new CurvedAnimation(
parent: animationControllers[index],
curve: Curves.elasticOut,
),
),
),
Expanded(
child: Container(
alignment: alignment,
padding: EdgeInsets.all(5),
child: child,
),
),
],
),
),
);
},
),
),
elevation: widget.tabsElevation,
shadowColor: widget.tabsShadowColor,
shape: BeveledRectangleBorder(),
),
Expanded(
child: PageView.builder(
scrollDirection: widget.contentScrollAxis,
physics: pageScrollPhysics,
onPageChanged: (index) {
if (_changePageByTapView == false ||
_changePageByTapView == null) {
_selectTab(index);
}
if (_selectedIndex == index) {
_changePageByTapView = null;
}
setState(() {});
},
controller: pageController,
// the number of pages
itemCount: widget.contents.length,
// building pages
itemBuilder: (BuildContext context, int index) {
return widget.contents[index];
},
),
),
],
),
),
],
),
);
}
void _selectTab(index) {
_selectedIndex = index;
for (AnimationController animationController in animationControllers) {
animationController.reset();
}
animationControllers[index].forward();
}
}

To Customise According To Your App Theme change Following Parameters:

To Diffrent Animations For Tab Indicator On Selection Of Tabs Check Below Curve Classes From Flutter

https://api.flutter.dev/flutter/animation/Curves-class.html

And Replace

this.changePageCurve = Curves.easeInOut,

To Change Animations.

this.tabsWidth = 200,
this.itemExtent = 50,
this.indicatorWidth = 5,
this.direction = TextDirection.ltr,
this.indicatorColor = Colors.blueGrey,
this.disabledChangePageFromContentView = false,
this.contentScrollAxis = Axis.horizontal,
this.selectedTabBackgroundColor = Colors.white,
this.unselectedTabBackgroundColor = const Color(0xfff8f8f8),
this.dividerColor = const Color(0xffe5e5e5),
this.changePageCurve = Curves.easeInOut,
this.changePageDuration = const Duration(milliseconds: 300),
this.tabsShadowColor = Colors.black54,
this.tabsElevation = 2.0}

Now Lets Create Custom Tabs and Their Contents tobe Shown When Selected.

  1. Create File personaltabs.dart
  2. Paste Following Code
  3. And Create Content Class these Classes Will Be The Content Shown To Right On Tab Selection.
import 'package:flutter/material.dart';

class PersonalTabsPage extends StatefulWidget {
@override
_PersonalTabsPageState createState() => new _PersonalTabsPageState();
}
class _PersonalTabsPageState extends State<PersonalTabsPage> {

@override
Widget build(BuildContext context) {
return new Scaffold(
//Current page to be changed from other classes too?
body: SafeArea(
child: Container(
child: VerticalTabs(
tabsWidth: 80,
direction: TextDirection.ltr,
contentScrollAxis: Axis.vertical,
changePageDuration: Duration(milliseconds: 500),
tabs: <Tab>[
Tab(child: Text('Components')),
Tab(child: Text('Headers & Footers')),
Tab(child: Text('Forms & Contacts')),
Tab(child: Text('Contact lists')),
Tab(child: Text('Lists & Grids')),
Tab(child: Text('Cards')),
Tab(child: Text('Slide Menus')),
Tab(child: Text('Blogs & Social Media')),
Tab(child: Text('Notifications')),
Tab(child: Text('Numeric Keyboards')),
Tab(child: Text('Dialogs')),
Tab(child: Text('Chats')),
Tab(child: Text('Slideshows')),
Tab(child: Text('Maps')),
Tab(child: Text('Examples')),


],
contents: <Widget>[
ComponentsPage(),
HeadderFooterPage(),
FormsContactPage(),
ContactListPage(),
ListGridPage(),
CardsPage(),
SideMenuPage(),
BlogSocialPage(),
NotificationsPage(),
NumericKeysPage(),
DialogsPage(),
ChatsPage(),
SlideShowPage(),
MapsPage(),
ExamplesPage(),


],
),
)),
);
}
}

Now Lets Place It In Main File.

import 'package:components/components/personaltabs.dart';
import 'package:flutter/material.dart';

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,

),
home: MyHomePage(),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);

final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

@override
Widget build(BuildContext context) {
return Scaffold(
// appBar: AppBar(
// title: Text(widget.title),
// ),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0),
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey,
offset: Offset(0.0, 1.0), //(x,y)
blurRadius: 6.0,
),
],
),
width: MediaQuery.of(context).size.width,
height:60,
child:Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Container(
child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(width:10),
//place Your Logo Image From Assets
Image.asset('crown.png',
height: 50,
fit:BoxFit.fill
),
SizedBox(width:10),
Text(
'Flutter Wireframing',
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 14,
color: Colors.black87,
)
),
]),
),
Text(
'By MassiveSuccess',
textDirection: TextDirection.ltr,
style: TextStyle(
fontSize: 14,
color: Colors.black87,
)
),
]
)
),

Expanded(
child: PersonalTabsPage(),
),
],
),
),
);
}
}

PersonalTabsPage()

Is The Class That Will Show Your Tabs And Contents Page Wherever You Would Like To View It.

This Is It You Have Got Yourself An Vertical Tabs That Shows Content To The Right Of The container When Selected. Mostly Applicable For Admin Dashboard UI, Desktop UI , Also Work On Flutter Web UI.

--

--

Kamlesh Paikrao

Web Developer, Online Advertiser, And Social Media Manager.