Skip to content

Instantly share code, notes, and snippets.

@nehal076
Last active October 7, 2022 10:57
Show Gist options
  • Save nehal076/ded70a5092034eed86dd9a801c74fca9 to your computer and use it in GitHub Desktop.
Save nehal076/ded70a5092034eed86dd9a801c74fca9 to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
void main() {
runApp(
const MaterialApp(
home: Nav(),
),
);
}
class Nav extends StatefulWidget {
const Nav({super.key});
@override
State<Nav> createState() => _NavState();
}
class _NavState extends State<Nav> {
int _selectedIndex = 0;
void _onItemTap(int index) {
setState(() {
_selectedIndex = index;
});
}
final List<Widget> _widgetOptions = [
const Center(child: Text('Home')),
const Center(child: Text('Explore')),
const Center(child: Text('Edit')),
const Center(child: Text('Notifications')),
const Center(child: Text('Profile')),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _widgetOptions.elementAt(_selectedIndex),
bottomNavigationBar: CurvedNavigationBar(
backgroundColor: Colors.grey.shade200,
buttonBackgroundColor: Colors.blueAccent.shade100,
color: Colors.white,
items: const [
Icon(Icons.home, color: Colors.black, size: 25),
Icon(Icons.search, color: Colors.black, size: 25),
Icon(Icons.create_outlined, color: Colors.black, size: 25),
Icon(Icons.heart_broken_rounded, size: 25),
Icon(Icons.person_outline, color: Colors.black, size: 25),
],
index: _selectedIndex,
onTap: _onItemTap,
),
);
}
}
typedef LetIndexPage = bool Function(int value);
class CurvedNavigationBar extends StatefulWidget {
final List<Widget> items;
final int index;
final Color color;
final Color? buttonBackgroundColor;
final Color backgroundColor;
final ValueChanged<int>? onTap;
final LetIndexPage letIndexChange;
final Curve animationCurve;
final Duration animationDuration;
final double height;
CurvedNavigationBar({
Key? key,
required this.items,
this.index = 0,
this.color = Colors.white,
this.buttonBackgroundColor,
this.backgroundColor = Colors.blueAccent,
this.onTap,
LetIndexPage? letIndexChange,
this.animationCurve = Curves.easeOut,
this.animationDuration = const Duration(milliseconds: 600),
this.height = 75.0,
}) : letIndexChange = letIndexChange ?? ((_) => true),
assert(items.isNotEmpty),
assert(0 <= index && index < items.length),
assert(0 <= height && height <= 75.0),
super(key: key);
@override
CurvedNavigationBarState createState() => CurvedNavigationBarState();
}
class CurvedNavigationBarState extends State<CurvedNavigationBar>
with SingleTickerProviderStateMixin {
late double _startingPos;
int _endingIndex = 0;
late double _pos;
double _buttonHide = 0;
late Widget _icon;
late AnimationController _animationController;
late int _length;
@override
void initState() {
super.initState();
_icon = widget.items[widget.index];
_length = widget.items.length;
_pos = widget.index / _length;
_startingPos = widget.index / _length;
_animationController = AnimationController(vsync: this, value: _pos);
_animationController.addListener(() {
setState(() {
_pos = _animationController.value;
final endingPos = _endingIndex / widget.items.length;
final middle = (endingPos + _startingPos) / 2;
if ((endingPos - _pos).abs() < (_startingPos - _pos).abs()) {
_icon = widget.items[_endingIndex];
}
_buttonHide =
(1 - ((middle - _pos) / (_startingPos - middle)).abs()).abs();
});
});
}
@override
void didUpdateWidget(CurvedNavigationBar oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.index != widget.index) {
final newPosition = widget.index / _length;
_startingPos = _pos;
_endingIndex = widget.index;
_animationController.animateTo(newPosition,
duration: widget.animationDuration, curve: widget.animationCurve);
}
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Container(
color: widget.backgroundColor,
height: widget.height,
child: Stack(
clipBehavior: Clip.none,
alignment: Alignment.bottomCenter,
children: <Widget>[
Positioned(
bottom: -40 - (75.0 - widget.height),
left: Directionality.of(context) == TextDirection.rtl
? null
: _pos * size.width,
right: Directionality.of(context) == TextDirection.rtl
? _pos * size.width
: null,
width: size.width / _length,
child: Center(
child: Transform.translate(
offset: Offset(
0,
-(1 - _buttonHide) * 80,
),
child: Material(
color: widget.buttonBackgroundColor ?? widget.color,
type: MaterialType.circle,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: _icon,
),
),
),
),
),
Positioned(
left: 0,
right: 0,
bottom: 0 - (75.0 - widget.height),
child: CustomPaint(
painter: NavCustomPainter(
_pos, _length, widget.color, Directionality.of(context)),
child: Container(
height: 75.0,
),
),
),
Positioned(
left: 0,
right: 0,
bottom: 0 - (75.0 - widget.height),
child: SizedBox(
height: 100.0,
child: Row(
children: widget.items.map((item) {
return NavButton(
onTap: _buttonTap,
position: _pos,
length: _length,
index: widget.items.indexOf(item),
child: Center(child: item),
);
}).toList())),
),
],
),
);
}
void setPage(int index) {
_buttonTap(index);
}
void _buttonTap(int index) {
if (!widget.letIndexChange(index)) {
return;
}
if (widget.onTap != null) {
widget.onTap!(index);
}
final newPosition = index / _length;
setState(() {
_startingPos = _pos;
_endingIndex = index;
_animationController.animateTo(newPosition,
duration: widget.animationDuration, curve: widget.animationCurve);
});
}
}
class NavCustomPainter extends CustomPainter {
late double loc;
late double s;
Color color;
TextDirection textDirection;
NavCustomPainter(
double startingLoc, int itemsLength, this.color, this.textDirection) {
final span = 1.0 / itemsLength;
s = 0.2;
double l = startingLoc + (span - s) / 2;
loc = textDirection == TextDirection.rtl ? 0.8 - l : l;
}
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = color
..style = PaintingStyle.fill;
final path = Path()
..moveTo(0, 0)
..lineTo((loc - 0.1) * size.width, 0)
..cubicTo(
(loc + s * 0.20) * size.width,
size.height * 0.05,
loc * size.width,
size.height * 0.60,
(loc + s * 0.50) * size.width,
size.height * 0.60,
)
..cubicTo(
(loc + s) * size.width,
size.height * 0.60,
(loc + s - s * 0.20) * size.width,
size.height * 0.05,
(loc + s + 0.1) * size.width,
0,
)
..lineTo(size.width, 0)
..lineTo(size.width, size.height)
..lineTo(0, size.height)
..close();
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return this != oldDelegate;
}
}
class NavButton extends StatelessWidget {
final double position;
final int length;
final int index;
final ValueChanged<int> onTap;
final Widget child;
const NavButton({
super.key,
required this.onTap,
required this.position,
required this.length,
required this.index,
required this.child,
});
@override
Widget build(BuildContext context) {
final desiredPosition = 1.0 / length * index;
final difference = (position - desiredPosition).abs();
final verticalAlignment = 1 - length * difference;
final opacity = length * difference;
return Expanded(
child: GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
onTap(index);
},
child: SizedBox(
height: 75.0,
child: Transform.translate(
offset: Offset(
0, difference < 1.0 / length ? verticalAlignment * 40 : 0),
child: Opacity(
opacity: difference < 1.0 / length * 0.99 ? opacity : 1.0,
child: child,
),
),
),
),
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment