Skip to content

Commit

Permalink
Custom bottom navigation implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
Thesmader committed Apr 19, 2020
1 parent 3cc4e96 commit 98c5854
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 2 deletions.
2 changes: 1 addition & 1 deletion gogrocy/lib/core/services/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Apis {
var client = new http.Client();
bool connectionState = await checkStatus();
if (connectionState) //TODO: Add a proper else return
{
{
var products = List<Product>();
var response = await client.post(allProducts);
var parsed = json.decode(response.body) as List<dynamic>;
Expand Down
11 changes: 10 additions & 1 deletion gogrocy/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@ import 'package:gogrocy/core/services/navigation_service.dart';
import 'package:gogrocy/service_locator.dart';
import 'package:gogrocy/ui/router.dart';
import 'package:gogrocy/ui/views/startup_view.dart';
import 'package:gogrocy/ui/widgets/navbar/bottom_navbar.dart';

void main() {
setupLocator();
runApp(GoGrocyApp());
}

class GoGrocyApp extends StatelessWidget {
class GoGrocyApp extends StatefulWidget {
@override
_GoGrocyAppState createState() => _GoGrocyAppState();
}

class _GoGrocyAppState extends State<GoGrocyApp> {
int _page = 0;
GlobalKey _bottomNavigationKey = GlobalKey();

@override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays(
Expand Down
207 changes: 207 additions & 0 deletions gogrocy/lib/ui/widgets/navbar/bottom_navbar.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import 'package:flutter/material.dart';
import 'package:gogrocy/ui/widgets/navbar/nav_button.dart';
import 'package:gogrocy/ui/widgets/navbar/nav_custom_painter.dart';
import 'package:meta/meta.dart';
import 'package:gogrocy/ui/shared/constants.dart' as constants;

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 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,
this.animationCurve = Curves.easeOut,
this.animationDuration = const Duration(milliseconds: 600),
this.height = 75.0,
})
: assert(items != null),
assert(items.length >= 1),
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 {
double _startingPos;
int _endingIndex = 0;
double _pos;
double _buttonHide = 0;
Widget _icon;
AnimationController _animationController;
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) {
constants.mediaQueryData = MediaQuery.of(context);
Size size = MediaQuery
.of(context)
.size;
return Container(
color: widget.backgroundColor,
height: widget.height,
child: Stack(
overflow: Overflow.visible,
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: EdgeInsets.all(15.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: 70.0,
child: Row(
children: widget.items.map((item) {
return NavButton(
onTap: _buttonTap,
position: _pos,
length: _length,
index: widget.items.indexOf(item),
child: item,
);
}).toList())),
),
],
),
);
}

void setPage(int index) {
_buttonTap(index);
}

void _buttonTap(int index) {
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 MyClipper extends CustomClipper<Path> {
final double left;
final double radius;
final int itemsLength;
final AnimationController controller;

MyClipper(this.left, this.radius, this.controller, this.itemsLength);

@override
Path getClip(Size size) {
Path path = Path();
path.moveTo(0, 0);
path.lineTo(left + 42, 0);
path.arcToPoint(Offset(left + radius * 2, 0),
clockwise: false, radius: Radius.circular(1));
path.lineTo(size.width, 0);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
path.close();
return path;
}

@override
bool shouldReclip(CustomClipper<Path> oldClipper) {
return true;
}
}
36 changes: 36 additions & 0 deletions gogrocy/lib/ui/widgets/navbar/nav_button.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';

class NavButton extends StatelessWidget {
final double position;
final int length;
final int index;
final ValueChanged<int> onTap;
final Widget child;

NavButton({this.onTap, this.position, this.length, this.index, 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: Container(
height: 75.0,
child: Transform.translate(
offset: Offset(
0, difference < 1.0 / length ? verticalAlignment * 50 : 0),
child: Opacity(
opacity: difference < 1.0 / length * 0.99 ? opacity : 1.0,
child: child),
)),
),
);
}
}
39 changes: 39 additions & 0 deletions gogrocy/lib/ui/widgets/navbar/nav_custom_painter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import 'package:flutter/material.dart';

class NavCustomPainter extends CustomPainter {
double loc;
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) * size.width, 0)
..arcToPoint(Offset((loc + s) * size.width, 0),
clockwise: false, radius: Radius.circular(20))
..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;
}
}

0 comments on commit 98c5854

Please sign in to comment.