motion_tab_bar_v2 0.4.0
motion_tab_bar_v2: ^0.4.0 copied to clipboard
An animated Bottom Navigation Bar for Flutter apps, icon animates into place, colors are customizable.
import 'package:flutter/material.dart';
import 'package:motion_tab_bar_v2/motion-tab-bar.dart';
import 'package:motion_tab_bar_v2/motion-badge.widget.dart';
import 'package:motion_tab_bar_v2/motion-tab-controller.dart';
import 'package:flutter_svg/flutter_svg.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Motion Tab Bar v2 Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Motion Tab Bar v2 Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, this.title});
final String? title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
// TabController? _tabController;
MotionTabBarController? _motionTabBarController;
@override
void initState() {
super.initState();
//// Use normal tab controller
// _tabController = TabController(
// initialIndex: 1,
// length: 4,
// vsync: this,
// );
//// use "MotionTabBarController" to replace with "TabController", if you need to programmatically change the tab
_motionTabBarController = MotionTabBarController(
initialIndex: 1,
length: 4,
vsync: this,
);
}
@override
void dispose() {
super.dispose();
_motionTabBarController!.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title!),
backgroundColor: Colors.orange,
),
bottomNavigationBar: MotionTabBar(
controller: _motionTabBarController, // ADD THIS if you need to change your tab programmatically
initialSelectedTab: "Home",
useSafeArea: true, // default: true, apply safe area wrapper
labelAlwaysVisible: true, // default: false, set to "true" if you need to always show labels
labels: const ["Dashboard", "Home", "Profile", "Settings"],
//// use default icon (with IconData)
// icons: const [
// Icons.dashboard,
// Icons.home,
// Icons.people_alt,
// Icons.settings,
// ],
// use custom widget as display Icon
iconWidgets: [
_generateCustomIcon(
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M520-640v-160q0-17 11.5-28.5T560-840h240q17 0 28.5 11.5T840-800v160q0 17-11.5 28.5T800-600H560q-17 0-28.5-11.5T520-640ZM120-480v-320q0-17 11.5-28.5T160-840h240q17 0 28.5 11.5T440-800v320q0 17-11.5 28.5T400-440H160q-17 0-28.5-11.5T120-480Zm400 320v-320q0-17 11.5-28.5T560-520h240q17 0 28.5 11.5T840-480v320q0 17-11.5 28.5T800-120H560q-17 0-28.5-11.5T520-160Zm-400 0v-160q0-17 11.5-28.5T160-360h240q17 0 28.5 11.5T440-320v160q0 17-11.5 28.5T400-120H160q-17 0-28.5-11.5T120-160Zm80-360h160v-240H200v240Zm400 320h160v-240H600v240Zm0-480h160v-80H600v80ZM200-200h160v-80H200v80Zm160-320Zm240-160Zm0 240ZM360-280Z"/></svg>'),
_generateCustomIcon(
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M240-200h120v-200q0-17 11.5-28.5T400-440h160q17 0 28.5 11.5T600-400v200h120v-360L480-740 240-560v360Zm-80 0v-360q0-19 8.5-36t23.5-28l240-180q21-16 48-16t48 16l240 180q15 11 23.5 28t8.5 36v360q0 33-23.5 56.5T720-120H560q-17 0-28.5-11.5T520-160v-200h-80v200q0 17-11.5 28.5T400-120H240q-33 0-56.5-23.5T160-200Zm320-270Z"/></svg>'),
_generateCustomIcon(
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-480q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47ZM160-240v-32q0-34 17.5-62.5T224-378q62-31 126-46.5T480-440q66 0 130 15.5T736-378q29 15 46.5 43.5T800-272v32q0 33-23.5 56.5T720-160H240q-33 0-56.5-23.5T160-240Zm80 0h480v-32q0-11-5.5-20T700-306q-54-27-109-40.5T480-360q-56 0-111 13.5T260-306q-9 5-14.5 14t-5.5 20v32Zm240-320q33 0 56.5-23.5T560-640q0-33-23.5-56.5T480-720q-33 0-56.5 23.5T400-640q0 33 23.5 56.5T480-560Zm0-80Zm0 400Z"/></svg>'),
_generateCustomIcon(
'<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M433-80q-27 0-46.5-18T363-142l-9-66q-13-5-24.5-12T307-235l-62 26q-25 11-50 2t-39-32l-47-82q-14-23-8-49t27-43l53-40q-1-7-1-13.5v-27q0-6.5 1-13.5l-53-40q-21-17-27-43t8-49l47-82q14-23 39-32t50 2l62 26q11-8 23-15t24-12l9-66q4-26 23.5-44t46.5-18h94q27 0 46.5 18t23.5 44l9 66q13 5 24.5 12t22.5 15l62-26q25-11 50-2t39 32l47 82q14 23 8 49t-27 43l-53 40q1 7 1 13.5v27q0 6.5-2 13.5l53 40q21 17 27 43t-8 49l-48 82q-14 23-39 32t-50-2l-60-26q-11 8-23 15t-24 12l-9 66q-4 26-23.5 44T527-80h-94Zm7-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>'),
],
// optional badges, length must be same with labels
badges: [
// Default Motion Badge Widget
const MotionBadgeWidget(
text: '99+',
textColor: Colors.white, // optional, default to Colors.white
color: Colors.blue, // optional, default to Colors.red
size: 18, // optional, default to 18
),
// custom badge Widget
Container(
color: Colors.black,
padding: const EdgeInsets.all(2),
child: const Text(
'48',
style: TextStyle(
fontSize: 14,
color: Colors.white,
),
),
),
// allow null
null,
// Default Motion Badge Widget with indicator only
const MotionBadgeWidget(
isIndicator: true,
color: Colors.blue, // optional, default to Colors.red
size: 5, // optional, default to 5,
show: true, // true / false
),
],
tabSize: 50,
tabBarHeight: 55,
textStyle: const TextStyle(
fontSize: 12,
color: Colors.black,
fontWeight: FontWeight.w500,
),
// tabIconColor: Colors.blue[600],
tabIconSize: 28.0,
tabIconSelectedSize: 32.0,
tabSelectedColor: Colors.white,
tabIconSelectedColor: Colors.black,
tabBarColor: Colors.orange,
onTabItemSelected: (int value) {
setState(() {
_motionTabBarController!.index = value;
});
},
),
body: TabBarView(
physics: const NeverScrollableScrollPhysics(), // swipe navigation handling is not supported
controller: _motionTabBarController,
children: <Widget>[
MainPageContentComponent(title: "Dashboard Page", controller: _motionTabBarController!),
MainPageContentComponent(title: "Home Page", controller: _motionTabBarController!),
MainPageContentComponent(title: "Profile Page", controller: _motionTabBarController!),
MainPageContentComponent(title: "Settings Page", controller: _motionTabBarController!),
],
),
);
}
Widget _generateCustomIcon(String svgString) {
return Container(
height: 40,
width: 40,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(20),
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(4.0),
child: SvgPicture.string(
svgString,
width: 24,
height: 24,
colorFilter: ColorFilter.mode(Colors.black, BlendMode.srcIn),
),
),
),
);
}
}
class MainPageContentComponent extends StatelessWidget {
const MainPageContentComponent({
required this.title,
required this.controller,
super.key,
});
final String title;
final MotionTabBarController controller;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 10,
children: [
Text(title, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
const Text('Go to "X" page programmatically'),
ElevatedButton(
onPressed: () => controller.index = 0,
child: const Text('Dashboard Page'),
),
ElevatedButton(
onPressed: () => controller.index = 1,
child: const Text('Home Page'),
),
ElevatedButton(
onPressed: () => controller.index = 2,
child: const Text('Profile Page'),
),
ElevatedButton(
onPressed: () => controller.index = 3,
child: const Text('Settings Page'),
),
],
),
);
}
}