Skip to content

Instantly share code, notes, and snippets.

@ayoubzulfiqar
Created August 9, 2023 10:37
Show Gist options
  • Save ayoubzulfiqar/d9b032664ce1f196fa282dcd4d312f22 to your computer and use it in GitHub Desktop.
Save ayoubzulfiqar/d9b032664ce1f196fa282dcd4d312f22 to your computer and use it in GitHub Desktop.
This Is Tab but little bit fancy
/// A Widget that displays a Bottom Navigation Bar with smooth animation.
/// It is a wrapper around [BottomNavigationBar]
/// [RevelTabBar] is a widget that displays a horizontal row of tabs, one tab at a time.
/// The tabs are individually titled and, when tapped, switch to that tab.
class RevelTabBar extends StatelessWidget {
RevelTabBar({
Key? key,
this.selectedIndex = 0,
this.height = 60,
this.showElevation = true,
this.iconSize = 20,
this.backgroundColor,
this.animationDuration = const Duration(milliseconds: 170),
this.animationCurve = Curves.linear,
this.shadows = const [
BoxShadow(
color: Colors.black12,
blurRadius: 3,
),
],
required this.items,
required this.onItemSelected,
}) : super(key: key) {
assert(height >= 55 && height <= 100);
assert(items.length >= 2 && items.length <= 5);
assert(iconSize >= 15 && iconSize <= 50);
}
final Curve animationCurve;
final Duration animationDuration;
final Color? backgroundColor;
final double height;
final double iconSize;
final List<RevelTabBarItem> items;
final ValueChanged<int> onItemSelected;
final int selectedIndex;
final List<BoxShadow> shadows;
final bool showElevation;
@override
Widget build(BuildContext context) {
final bg = (backgroundColor == null)
? Theme.of(context).colorScheme.background
: backgroundColor;
return Container(
decoration: BoxDecoration(
color: bg,
boxShadow: showElevation ? shadows : [],
),
child: SafeArea(
child: Container(
width: double.infinity,
height: height + iconSizeEffectCalculator(iconSize),
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: items.map((item) {
var index = items.indexOf(item);
return Expanded(
child: GestureDetector(
onTap: () => onItemSelected(index),
child: _FlashTabBarItem(
item: item,
tabBarHeight: height,
iconSize: iconSize,
isSelected: index == selectedIndex,
backgroundColor: bg!,
animationDuration: animationDuration,
animationCurve: animationCurve,
),
),
);
}).toList(),
),
),
),
);
}
/// A method that calculate the effect of [iconSize] on the [height] of the Bottom navigation bar
double iconSizeEffectCalculator(double size) => size > 30
? size * 1.2
: size > 10
? size * .6
: 0;
}
/// A single tab in the [RevelTabBar]. A tab has a title and an icon. The title is displayed when the item is not selected. The icon is displayed when the item is selected. Tabs are always used in conjunction with a [RevelTabBar].
class RevelTabBarItem {
RevelTabBarItem({
required this.icon,
required this.title,
this.activeColor = const Color(0xff272e81),
this.inactiveColor = const Color(0xff9496c1),
});
Color activeColor;
final Widget icon;
Color inactiveColor;
final Text title;
}
class _FlashTabBarItem extends StatelessWidget {
const _FlashTabBarItem(
{Key? key,
required this.item,
required this.isSelected,
required this.tabBarHeight,
required this.backgroundColor,
required this.animationDuration,
required this.animationCurve,
required this.iconSize})
: super(key: key);
final Curve animationCurve;
final Duration animationDuration;
final Color backgroundColor;
final double iconSize;
final bool isSelected;
final RevelTabBarItem item;
final double tabBarHeight;
@override
Widget build(BuildContext context) {
/// The icon is displayed when the item is not selected.
/// The title is displayed when the item is selected.
/// The icon and title are animated together.
/// The icon and title are animated in opposite directions.
return Container(
color: backgroundColor,
height: double.maxFinite,
child: Stack(
clipBehavior: Clip.hardEdge,
alignment: Alignment.center,
children: <Widget>[
AnimatedAlign(
duration: animationDuration,
alignment: isSelected ? Alignment.topCenter : Alignment.center,
child: AnimatedOpacity(
opacity: isSelected ? 1.0 : 1.0,
duration: animationDuration,
child: IconTheme(
data: IconThemeData(
size: iconSize,
color: isSelected
? item.activeColor.withOpacity(1)
: item.inactiveColor),
child: item.icon,
)),
),
AnimatedPositioned(
curve: animationCurve,
duration: animationDuration,
top: isSelected ? -2.0 * iconSize : tabBarHeight / 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
width: iconSize,
height: iconSize,
),
CustomPaint(
painter: _CustomPath(backgroundColor, iconSize),
child: SizedBox(
width: 80,
height: iconSize,
),
)
],
),
),
AnimatedAlign(
alignment:
isSelected ? Alignment.center : Alignment.bottomCenter,
duration: animationDuration,
curve: animationCurve,
child: AnimatedOpacity(
opacity: isSelected ? 1.0 : 0.0,
duration: animationDuration,
child: DefaultTextStyle.merge(
style: TextStyle(
color: item.activeColor,
fontWeight: FontWeight.bold,
),
child: item.title,
))),
Positioned(
bottom: 0,
child: CustomPaint(
painter: _CustomPath(backgroundColor, iconSize),
child: SizedBox(
width: 80,
height: iconSize,
),
)),
/// This is the selected item indicator
Align(
alignment: Alignment.bottomCenter,
child: AnimatedOpacity(
duration: animationDuration,
opacity: isSelected ? 1.0 : 0.0,
child: Container(
width: 5,
height: 5,
alignment: Alignment.bottomCenter,
margin: const EdgeInsets.all(5),
decoration: BoxDecoration(
color: item.activeColor,
borderRadius: BorderRadius.circular(2.5),
),
)),
)
],
));
}
}
/// A [CustomPainter] that draws a [RevelTabBar] background.
class _CustomPath extends CustomPainter {
_CustomPath(this.backgroundColor, this.iconSize);
final Color backgroundColor;
final double iconSize;
@override
void paint(Canvas canvas, Size size) {
Path path = Path();
Paint paint = Paint();
path.lineTo(0, 0);
path.lineTo(0, (iconSize * .2) * size.height);
path.lineTo(1.0 * size.width, (iconSize * .2) * size.height);
path.lineTo(1.0 * size.width, 1.0 * size.height);
path.lineTo(0, 0);
path.close();
paint.color = backgroundColor;
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return oldDelegate != this;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment