Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support changing index externally #41

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 76 additions & 35 deletions lib/src/fluid_nav_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import './fluid_nav_bar_item.dart';

typedef void FluidNavBarChangeCallback(int selectedIndex);

typedef Widget FluidNavBarItemBuilder(FluidNavBarIcon icon, FluidNavBarItem item);
typedef Widget FluidNavBarItemBuilder(
FluidNavBarIcon icon, FluidNavBarItem item);

/// A widget to display a fluid navigation bar with icon buttons.
///
Expand Down Expand Up @@ -52,9 +53,9 @@ class FluidNavBar extends StatefulWidget {
/// other widget
final double scaleFactor;

/// Default Index is used for setting up selected item on start of the application.
/// Current Index is used for setting up selected item on start of the application.
/// By default set to 0, meaning that item with index 0 will be selected.
final int defaultIndex;
final int currentIndex;

final FluidNavBarItemBuilder itemBuilder;

Expand All @@ -65,19 +66,22 @@ class FluidNavBar extends StatefulWidget {
this.style,
this.animationFactor = 1.0,
this.scaleFactor = 1.2,
this.defaultIndex = 0,
this.currentIndex = 0,
FluidNavBarItemBuilder? itemBuilder})
: this.itemBuilder = itemBuilder ?? _identityBuilder,
assert(icons.length > 1),
assert(currentIndex >= 0),
super(key: key);

@override
State createState() => _FluidNavBarState();

static Widget _identityBuilder(FluidNavBarIcon icon, FluidNavBarItem item) => item;
static Widget _identityBuilder(FluidNavBarIcon icon, FluidNavBarItem item) =>
item;
}

class _FluidNavBarState extends State<FluidNavBar> with TickerProviderStateMixin {
class _FluidNavBarState extends State<FluidNavBar>
with TickerProviderStateMixin {
int _currentIndex = 0;

late final AnimationController _xController;
Expand All @@ -87,10 +91,12 @@ class _FluidNavBarState extends State<FluidNavBar> with TickerProviderStateMixin
void initState() {
super.initState();

_currentIndex = widget.defaultIndex;
_currentIndex = widget.currentIndex;

_xController = AnimationController(vsync: this, animationBehavior: AnimationBehavior.preserve);
_yController = AnimationController(vsync: this, animationBehavior: AnimationBehavior.preserve);
_xController = AnimationController(
vsync: this, animationBehavior: AnimationBehavior.preserve);
_yController = AnimationController(
vsync: this, animationBehavior: AnimationBehavior.preserve);

Listenable.merge([_xController, _yController]).addListener(() {
setState(() {});
Expand All @@ -99,12 +105,26 @@ class _FluidNavBarState extends State<FluidNavBar> with TickerProviderStateMixin

@override
void didChangeDependencies() {
_xController.value = _indexToPosition(_currentIndex) / MediaQuery.of(context).size.width;
_xController.value =
_indexToPosition(_currentIndex) / MediaQuery.of(context).size.width;
_yController.value = 1.0;

super.didChangeDependencies();
}

@override
void didUpdateWidget(FluidNavBar oldWidget) {
super.didUpdateWidget(oldWidget);

var index = widget.currentIndex;

if (_currentIndex == index || _xController.isAnimating) return;
if (index != oldWidget.currentIndex) {
_currentIndex = index;
_animateIndexChange(index);
}
}

@override
void dispose() {
_xController.dispose();
Expand Down Expand Up @@ -134,7 +154,9 @@ class _FluidNavBarState extends State<FluidNavBar> with TickerProviderStateMixin
top: 0,
width: _getButtonContainerWidth(),
height: height,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: _buildButtons()),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: _buildButtons()),
),
],
),
Expand Down Expand Up @@ -166,8 +188,12 @@ class _FluidNavBarState extends State<FluidNavBar> with TickerProviderStateMixin
entry.value.icon,
_currentIndex == entry.key,
() => _handleTap(entry.key),
entry.value.selectedForegroundColor ?? widget.style?.iconSelectedForegroundColor ?? Colors.black,
entry.value.unselectedForegroundColor ?? widget.style?.iconUnselectedForegroundColor ?? Colors.grey,
entry.value.selectedForegroundColor ??
widget.style?.iconSelectedForegroundColor ??
Colors.black,
entry.value.unselectedForegroundColor ??
widget.style?.iconUnselectedForegroundColor ??
Colors.grey,
entry.value.backgroundColor ??
widget.style?.iconBackgroundColor ??
widget.style?.barBackgroundColor ??
Expand Down Expand Up @@ -195,26 +221,33 @@ class _FluidNavBarState extends State<FluidNavBar> with TickerProviderStateMixin
final appWidth = MediaQuery.of(context).size.width;
final buttonsWidth = _getButtonContainerWidth();
final startX = (appWidth - buttonsWidth) / 2;
return startX + index.toDouble() * buttonsWidth / buttonCount + buttonsWidth / (buttonCount * 2.0);
return startX +
index.toDouble() * buttonsWidth / buttonCount +
buttonsWidth / (buttonCount * 2.0);
}

void _handleTap(int index) {
if (_currentIndex == index || _xController.isAnimating) return;

setState(() {
_currentIndex = index;
});

void _animateIndexChange(int index) {
_yController.value = 1.0;
_xController.animateTo(_indexToPosition(index) / MediaQuery.of(context).size.width,
_xController.animateTo(
_indexToPosition(index) / MediaQuery.of(context).size.width,
duration: Duration(milliseconds: 620) * widget.animationFactor);
Future.delayed(
Duration(milliseconds: 500) * widget.animationFactor,
() {
_yController.animateTo(1.0, duration: Duration(milliseconds: 1200) * widget.animationFactor);
_yController.animateTo(1.0,
duration: Duration(milliseconds: 1200) * widget.animationFactor);
},
);
_yController.animateTo(0.0, duration: Duration(milliseconds: 300) * widget.animationFactor);
_yController.animateTo(0.0,
duration: Duration(milliseconds: 300) * widget.animationFactor);
}

void _handleTap(int index) {
if (_currentIndex == index || _xController.isAnimating) return;

_currentIndex = index;

_animateIndexChange(index);

if (widget.onChange != null) {
widget.onChange!(index);
Expand Down Expand Up @@ -251,26 +284,32 @@ class _BackgroundCurvePainter extends CustomPainter {
// Paint two cubic bezier curves using various linear interpolations based off of the `_normalizedY` value
final norm = LinearPointCurve(0.5, 2.0).transform(_normalizedY) / 2;

final radius = Tween<double>(begin: _radiusTop, end: _radiusBottom).transform(norm);
final radius =
Tween<double>(begin: _radiusTop, end: _radiusBottom).transform(norm);
// Point colinear to the top edge of the background pane
final anchorControlOffset =
Tween<double>(begin: radius * _horizontalControlTop, end: radius * _horizontalControlBottom)
.transform(LinearPointCurve(0.5, 0.75).transform(norm));
final anchorControlOffset = Tween<double>(
begin: radius * _horizontalControlTop,
end: radius * _horizontalControlBottom)
.transform(LinearPointCurve(0.5, 0.75).transform(norm));
// Point that slides up and down depending on distance for the target x position
final dipControlOffset = Tween<double>(begin: radius * _pointControlTop, end: radius * _pointControlBottom)
final dipControlOffset = Tween<double>(
begin: radius * _pointControlTop, end: radius * _pointControlBottom)
.transform(LinearPointCurve(0.5, 0.8).transform(norm));
final y = Tween<double>(begin: _topY, end: _bottomY).transform(LinearPointCurve(0.2, 0.7).transform(norm));
final dist =
Tween<double>(begin: _topDistance, end: _bottomDistance).transform(LinearPointCurve(0.5, 0.0).transform(norm));
final y = Tween<double>(begin: _topY, end: _bottomY)
.transform(LinearPointCurve(0.2, 0.7).transform(norm));
final dist = Tween<double>(begin: _topDistance, end: _bottomDistance)
.transform(LinearPointCurve(0.5, 0.0).transform(norm));
final x0 = _x - dist / 2;
final x1 = _x + dist / 2;

final path = Path()
..moveTo(0, 0)
..lineTo(x0 - radius, 0)
..cubicTo(x0 - radius + anchorControlOffset, 0, x0 - dipControlOffset, y, x0, y)
..cubicTo(
x0 - radius + anchorControlOffset, 0, x0 - dipControlOffset, y, x0, y)
..lineTo(x1, y)
..cubicTo(x1 + dipControlOffset, y, x1 + radius - anchorControlOffset, 0, x1 + radius, 0)
..cubicTo(x1 + dipControlOffset, y, x1 + radius - anchorControlOffset, 0,
x1 + radius, 0)
..lineTo(size.width, 0)
..lineTo(size.width, size.height)
..lineTo(0, size.height);
Expand All @@ -282,6 +321,8 @@ class _BackgroundCurvePainter extends CustomPainter {

@override
bool shouldRepaint(_BackgroundCurvePainter oldPainter) {
return _x != oldPainter._x || _normalizedY != oldPainter._normalizedY || _color != oldPainter._color;
return _x != oldPainter._x ||
_normalizedY != oldPainter._normalizedY ||
_color != oldPainter._color;
}
}