How to animate progressbar color with Flutter - flutter

I tried to create an animated progressbar where the progress is filling up animatedly and also it changes it's color depending on the final value of the progress.
The first part is working properly, but I can't figure out what is wrong with the color animation. What am I missing?
I used flutter_rounded_progress_bar for displaying the progress bar.
import 'package:flutter/material.dart';
import 'package:flutter_rounded_progress_bar/flutter_rounded_progress_bar.dart';
import 'package:flutter_rounded_progress_bar/rounded_progress_bar_style.dart';
class MyLinearProgressIndicator extends StatefulWidget {
final double currentProgress;
final double height;
final Color foregroundColor;
final int duration = 500;
MyLinearProgressIndicator({
this.currentProgress,
this.height = 5,
this.foregroundColor = const Color(0xFFde8405)});
#override
_LinearProgressIndicatorState createState() =>
_LinearProgressIndicatorState();
}
class _LinearProgressIndicatorState extends State<MyLinearProgressIndicator> with
SingleTickerProviderStateMixin {
AnimationController progressController;
Animation<double> animation;
Animation<Color> colorAnimation;
CurvedAnimation curve;
#override
void initState() {
super.initState();
progressController = AnimationController(vsync: this, duration: Duration(milliseconds: widget.duration));
curve = CurvedAnimation(parent: progressController, curve: Curves.ease);
animation =
Tween<double>(begin: 0, end: widget.currentProgress).animate(curve)
..addListener(() {
setState(() {});
});
Color endColor;
if (widget.currentProgress <= 30) {
endColor = const Color(0xFFFF0000);
} else if (widget.currentProgress <= 50) {
endColor = const Color(0xFF00FF00);
} else {
endColor =const Color(0xFF0000FF);
}
colorAnimation =
ColorTween(begin: widget.foregroundColor, end: endColor).animate(curve)
..addListener(() {
setState(() {});
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
progressController.forward();
});
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Text("${widget.currentProgress.floor()}%",
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 12,
color: Color(0xFF333333))),
),
Container(
height: widget.height,
width: 100,
child: RoundedProgressBar(
milliseconds: widget.duration,
height: widget.height,
style: RoundedProgressBarStyle(
borderWidth: 0,
widthShadow: 0,
colorProgress: colorAnimation.value,
backgroundProgress: Color(0xFFEBEBEB)),
percent: animation.value,
)),
],
);
}
#override
void dispose() {
progressController.dispose();
super.dispose();
}
}

The issue here is that the current color value is being fetched from Animation<Color> with colorAnimation.value. A quick workaround here is to create a function that returns the color for ranges of values.
Color progressColor({required double value}) {
if (value > 0.60) {
return Colors.red;
} else if (value > 0.30) {
return Colors.orange;
} else {
return Colors.green;
}
}
and set the Color to the ProgressIndicator
RoundedProgressBar(
milliseconds: widget.duration,
height: widget.height,
style: RoundedProgressBarStyle(
borderWidth: 0,
widthShadow: 0,
colorProgress: progressColor(animation.value),
backgroundProgress: Color(0xFFEBEBEB)
),
percent: animation.value,
)

Related

Flutter - No BorderRadius assignment

When I build the application, I encounter such an error.
lib/input_page/pacman_slider.dart:41:7: Error: A value of type 'Animation<BorderRadius?>' can't be assigned to a variable of type 'Animation' because 'BorderRadius?' is nullable and 'BorderRadius' isn't.
'Animation' is from 'package:flutter/src/animation/animation.dart' ('../../Dev/flutter/packages/flutter/lib/src/animation/animation.dart').
'BorderRadius' is from 'package:flutter/src/painting/border_radius.dart' ('../../Dev/flutter/packages/flutter/lib/src/painting/border_radius.dart').
).animate(CurvedAnimation(
^
Error Code:
void initState() {
super.initState();
pacmanMovementController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_bordersAnimation = BorderRadiusTween(
begin: BorderRadius.circular(8.0),
end: BorderRadius.circular(50.0),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.0, 0.07),
));
}
Full Code:
const double _pacmanWidth = 21.0;
const double _sliderHorizontalMargin = 24.0;
const double _dotsLeftMargin = 8.0;
class PacmanSlider extends StatefulWidget {
final VoidCallback onSubmit;
final AnimationController submitAnimationController;
const PacmanSlider(
{Key? key,
required this.onSubmit,
required this.submitAnimationController})
: super(key: key);
#override
_PacmanSliderState createState() => _PacmanSliderState();
}
class _PacmanSliderState extends State<PacmanSlider>
with TickerProviderStateMixin {
double _pacmanPosition = 24.0;
late Animation<BorderRadius> _bordersAnimation;
late Animation<double> _submitWidthAnimation;
late AnimationController pacmanMovementController;
late Animation<double> pacmanAnimation;
#override
void initState() {
super.initState();
pacmanMovementController =
AnimationController(vsync: this, duration: Duration(milliseconds: 400));
_bordersAnimation = BorderRadiusTween(
begin: BorderRadius.circular(8.0),
end: BorderRadius.circular(50.0),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.0, 0.07),
));
}
#override
void dispose() {
pacmanMovementController.dispose();
super.dispose();
}
// double get width => _submitWidthAnimation?.value ?? 0.0;
double get width => _submitWidthAnimation.value;
#override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
_submitWidthAnimation = Tween<double>(
begin: constraints.maxWidth,
end: screenAwareSize(52.0, context),
).animate(CurvedAnimation(
parent: widget.submitAnimationController,
curve: Interval(0.05, 0.15),
));
return AnimatedBuilder(
animation: widget.submitAnimationController,
builder: (context, child) {
Decoration decoration = BoxDecoration(
borderRadius: _bordersAnimation.value,
color: Theme.of(context).primaryColor,
);
return Center(
child: Container(
height: screenAwareSize(52.0, context),
width: width,
decoration: decoration,
child: _submitWidthAnimation.isDismissed
? GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => _animatePacmanToEnd(),
child: Stack(
alignment: Alignment.centerRight,
children: <Widget>[
AnimatedDots(),
_drawDotCurtain(decoration),
_drawPacman(),
],
),
)
: Container(),
),
);
},
);
},
);
}
Widget _drawDotCurtain(Decoration decoration) {
if (width == 0.0) {
return Container();
}
double marginRight =
width - _pacmanPosition - screenAwareSize(_pacmanWidth / 2, context);
return Positioned.fill(
right: marginRight,
child: Container(decoration: decoration),
);
}
Widget _drawPacman() {
pacmanAnimation = _initPacmanAnimation();
return Positioned(
left: _pacmanPosition,
child: GestureDetector(
onHorizontalDragUpdate: (details) => _onPacmanDrag(width, details),
onHorizontalDragEnd: (details) => _onPacmanEnd(width, details),
child: PacmanIcon(),
),
);
}
Animation<double> _initPacmanAnimation() {
Animation<double> animation = Tween(
begin: _pacmanMinPosition(),
end: _pacmanMaxPosition(width),
).animate(pacmanMovementController);
animation.addListener(() {
setState(() {
_pacmanPosition = animation.value;
});
if (animation.status == AnimationStatus.completed) {
_onPacmanSubmit();
}
});
return animation;
}
_onPacmanSubmit() {
widget.onSubmit();
Future.delayed(Duration(seconds: 1), () => _resetPacman());
}
_onPacmanDrag(double width, DragUpdateDetails details) {
setState(() {
_pacmanPosition += details.delta.dx;
_pacmanPosition = math.max(_pacmanMinPosition(),
math.min(_pacmanMaxPosition(width), _pacmanPosition));
});
}
_onPacmanEnd(double width, DragEndDetails details) {
bool isOverHalf =
_pacmanPosition + screenAwareSize(_pacmanWidth / 2, context) >
0.5 * width;
if (isOverHalf) {
_animatePacmanToEnd();
} else {
_resetPacman();
}
}
_animatePacmanToEnd() {
pacmanMovementController.forward(
from: _pacmanPosition / _pacmanMaxPosition(width));
}
_resetPacman() {
if (this.mounted) {
setState(() => _pacmanPosition = _pacmanMinPosition());
}
}
double _pacmanMinPosition() =>
screenAwareSize(_sliderHorizontalMargin, context);
double _pacmanMaxPosition(double sliderWidth) =>
sliderWidth -
screenAwareSize(_sliderHorizontalMargin / 2 + _pacmanWidth, context);
}
class AnimatedDots extends StatefulWidget {
#override
_AnimatedDotsState createState() => _AnimatedDotsState();
}
class _AnimatedDotsState extends State<AnimatedDots>
with TickerProviderStateMixin {
final int numberOfDots = 10;
final double minOpacity = 0.1;
final double maxOpacity = 0.5;
late AnimationController hintAnimationController;
#override
void initState() {
super.initState();
_initHintAnimationController();
hintAnimationController.forward();
}
#override
void dispose() {
hintAnimationController.dispose();
super.dispose();
}
void _initHintAnimationController() {
hintAnimationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 800),
);
hintAnimationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
Future.delayed(Duration(milliseconds: 800), () {
if (this.mounted) {
hintAnimationController.forward(from: 0.0);
}
});
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: screenAwareSize(
_sliderHorizontalMargin + _pacmanWidth + _dotsLeftMargin,
context),
right: screenAwareSize(_sliderHorizontalMargin, context)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(numberOfDots, _generateDot)
..add(Opacity(
opacity: maxOpacity,
child: Dot(size: 14.0),
)),
),
);
}
Widget _generateDot(int dotNumber) {
Animation animation = _initDotAnimation(dotNumber);
return AnimatedBuilder(
animation: animation,
builder: (context, child) => Opacity(
opacity: animation.value,
child: child,
),
child: Dot(size: 9.0),
);
}
Animation<double> _initDotAnimation(int dotNumber) {
double lastDotStartTime = 0.4;
double dotAnimationDuration = 0.5;
double begin = lastDotStartTime * dotNumber / numberOfDots;
double end = begin + dotAnimationDuration;
return SinusoidalAnimation(min: minOpacity, max: maxOpacity).animate(
CurvedAnimation(
parent: hintAnimationController,
curve: Interval(begin, end),
),
);
}
}
class SinusoidalAnimation extends Animatable<double> {
SinusoidalAnimation({required this.min, required this.max});
final double min;
final double max;
#override
double transform(double t) {
return min + (max - min) * math.sin(math.pi * t);
}
}
class Dot extends StatelessWidget {
final double size;
const Dot({Key? key, required this.size}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
height: screenAwareSize(size, context),
width: screenAwareSize(size, context),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
);
}
}
class PacmanIcon extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
right: screenAwareSize(16.0, context),
),
child: SvgPicture.asset(
'images/pacman.svg',
height: screenAwareSize(25.0, context),
width: screenAwareSize(21.0, context),
),
);
}
}
As you see in the attached capture, BorderRadiusTween has a nullable value of type BorderRadius?
I'd suggest to either declare _bordersAnimation as Animation<BorderRadius?>or just using an Animation<double> instead

Flutter: Image Resize Animation is laggy

I am trying to do an animation for an AppBar and the image is shrinking laggy.
You can see in this gif what I am speaking about:
Since it is gif, the entire animation looks a little laggy, but only the image actually is.
CODE
class _AuthAppBarState extends State<AuthAppBar> with TickerProviderStateMixin {
double _leftPoint = 0.0;
double _rightPoint = 70.0;
String _lastTriggeredType = '';
late AnimationController _animationController;
late Animation<double> leftPointAnimation;
late Animation<double> rightPointAnimation;
late Animation<double> titleAnimation;
void triggerAnimation(bool trigger) {
final double prevLeft = _leftPoint;
final double prevRight = _rightPoint;
final double prevTitleHeight = _lastTriggeredType == 'equal' ? 40 : 70;
final double newLeft = leftPoints[widget.type];
final double newRight = rightPoints[widget.type];
final double newTitleHeight = widget.type == 'equal' ? 40 : 70;
setState(() {
leftPointAnimation = Tween<double>(begin: prevLeft, end: newLeft)
.animate(_animationController);
rightPointAnimation = Tween<double>(begin: prevRight, end: newRight)
.animate(_animationController);
titleAnimation =
Tween<double>(begin: prevTitleHeight, end: newTitleHeight)
.animate(_animationController);
_rightPoint = rightPoints[widget.type];
_leftPoint = leftPoints[widget.type];
_lastTriggeredType = widget.type;
});
if (trigger && _animationController != null)
_animationController.forward(from: 0);
}
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 300),
);
triggerAnimation(false);
}
#override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final size = MediaQuery.of(context).size;
if (_animationController == null || titleAnimation == null) {
return Container();
}
if (_lastTriggeredType != widget.type) triggerAnimation(true);
// * ANIMATED CENTERED TITLE * \\
final Center centeredTitle = Center(
child: AnimatedBuilder(
animation: _animationController,
builder: (ctx, _) {
return SvgPicture.asset(
images['title'],
height: titleAnimation.value,
);
},
),
);
// * MAIN CONTAINER - defines size and color of clip path * \\
final container = Container(
color: theme.primaryColor,
child: AnimatedSize(
curve: Curves.decelerate,
duration: const Duration(seconds: 1),
child: Container(
height: widget.type == 'equal' ? 50 : size.height / 3,
child: centeredTitle,
),
vsync: this,
),
);
return AnimatedBuilder(
builder: (context, anim) {
return ClipPath(
clipper: AuthAppBarPath(
leftPoint: leftPointAnimation.value,
rightPoint: rightPointAnimation.value,
),
child: container,
);
},
animation: _animationController,
);
}
}
I tried to use AnimatedSize and AnimatedContainer. It works the same.
Thank you so much for all the time and help!!

How to increase counter value if i drag up and decrease if i drag down with Gesture Detector?

I have a counter where I want to increment and decrement a value while dragging up and down with a gesture detector. Everything works fine but I dont know where to call counter++ to increase and counter-- to decrease the value.
Here is how im doing it(most of the code is for spring physics animation) :
class DraggableCard extends StatefulWidget {
int count;
DraggableCard({this.count = 0});
#override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Alignment _dragAlignment = Alignment.center;
Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onVerticalDragStart: (details) {
_controller.stop();
},
onVerticalDragUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 0),
details.delta.dy / (size.height * 1),
);
});
if (details.delta.dx > 0) {
print("Dragging in +X direction");
widget.count++;
} else {
print("Dragging in -X direction");
widget.count--;
}
},
onVerticalDragEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Text(
widget.count.toString(),
style: TextStyle(
fontSize: 60.0,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
);
}
}
All you had to do was to check details.delta.dy instead of details.delta.dy. If details.delta.dy < 0 then widget.count++; else widget.count--;
Please see the code : [Note: I had to comment out some of the code to make it work.]
import 'package:flutter/material.dart';
final Color darkBlue = const Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: DraggableCard(),
),
),
);
}
}
class MyWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Text('Hello, World!', style: Theme.of(context).textTheme.headline4);
}
}
class DraggableCard extends StatefulWidget {
int count;
DraggableCard({this.count = 0});
#override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Alignment _dragAlignment = Alignment.center;
Animation<Alignment> _animation;
/// Calculates and runs a [SpringSimulation].
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
// Calculate the velocity relative to the unit interval, [0,1],
// used by the animation controller.
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
// const spring = SpringDescription(
// mass: 30,
// stiffness: 1,
// damping: 1,
// );
// final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
// _controller.animateWith(simulation);
}
#override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onVerticalDragStart: (details) {
_controller.stop();
},
onVerticalDragUpdate: (details) {
print("${details.delta.dx} ${details.delta.dy}");
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 0),
details.delta.dy / (size.height * 1),
);
});
if (details.delta.dy < 0) {
print("Dragging in +X direction");
widget.count++;
} else {
print("Dragging in -X direction");
widget.count--;
}
},
onVerticalDragEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Text(
widget.count.toString(),
style: const TextStyle(
fontSize: 60.0,
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
),
);
}
}

How to pass dynamic value to other widget using flutter

I want to create condition that if first api didn't finish load when click on bottom navigation tab it will not navigate to other tab. So I want to pass boolean that will change to true when api is loaded, the problem is I don't know how to pass this dynamic value to my custom Bottom Navigation Bar. Is there an other way instead of using global variable?
This is my code.
landingPage.dart
// This global variable is change to true when receive callback from api
bool isComplete = false;
class _NavigationItem {
_NavigationItem(this.iconFile, this.caption, this.page);
final String iconFile;
final String caption;
final Widget page;
}
class LandingPage extends StatefulWidget {
final String username;
LandingPage({Key key, this.username}) : super(key: key);
#override
_LandingPageState createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
int _selectedIndex = 0;
List<_NavigationItem> _navItems = [];
double _bottomNavBarHeight = 46;
double _iconSize = 26;
double _circleSize = 52;
int _animationDuration = 300;
double _circleStrokeWidth = 0;
BottomNavigationController _navigationController;
#override
void didChangeDependencies() {
// Context of a state is available to us from the moment the State loads its dependencies
// Since we need context object so it need to be accessed inside this overridden method.
_navItems = [
_NavigationItem(
"assets/icons/home.svg",
S.of(context).landing_nav_home,
HomePage(
username: widget.username,
callback: (value) { isComplete = value; }, // receiving callback data
)),
_NavigationItem("assets/icons/friend.svg",
S.of(context).landing_nav_friend, FriendPage()),
_NavigationItem(
"assets/icons/chat.svg", S.of(context).landing_nav_chat, ChatPage()),
_NavigationItem("assets/icons/widget.svg",
S.of(context).landing_nav_widget, WidgetPage()),
_NavigationItem("assets/icons/more.svg",
S.of(context).landing_nav_setting, SettingPage()),
];
super.didChangeDependencies();
}
#override
void initState() {
super.initState();
_navigationController = new BottomNavigationController(_selectedIndex);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Container(
padding: EdgeInsets.only(bottom: 90),
child: _navItems.elementAt(_selectedIndex).page,
),
_createBottomNavigationBar()
],
)
);
}
void _onNavigationBarItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
Widget _createBottomNavigationBar() {
return Align(alignment: Alignment.bottomCenter, child: _bottomNav());
}
Widget _bottomNav() {
List<TabItem> tabItems = List.of([
new TabItem(Icons.home_outlined, "", CommonColor.accent,
asset: "assets/icons/home.svg"),
new TabItem(Icons.person, "", CommonColor.accent,
asset: "assets/icons/friends.svg"),
new TabItem(Icons.chat_bubble_outline, "", CommonColor.accent),
new TabItem(Icons.widgets_outlined, "", CommonColor.accent),
new TabItem(Icons.more_horiz, "", CommonColor.accent),
]);
return BottomNavigation(
tabItems,
controller: _navigationController,
barHeight: _bottomNavBarHeight,
iconsSize: _iconSize,
circleSize: _circleSize,
selectedIconColor: Colors.white,
normalIconColor: CommonColor.accent,
circleStrokeWidth: _circleStrokeWidth,
barBackgroundColor: Colors.white,
animationDuration: Duration(milliseconds: _animationDuration),
selectedCallback: (int selectedPos) {
print("selected: $_selectedIndex");
_onNavigationBarItemTapped(selectedPos);
},
);
}
#override
void dispose() {
super.dispose();
_navigationController.dispose();
}
}
BottomNavigation.dart
typedef BottomNavSelectedCallback = Function(int selectedPos);
class BottomNavigation extends StatefulWidget {
final List<TabItem> tabItems;
final int selectedPos;
final double barHeight;
final double padding;
final Color barBackgroundColor;
final double circleSize;
final double circleStrokeWidth;
final double iconsSize;
final Color selectedIconColor;
final Color normalIconColor;
final Duration animationDuration;
final BottomNavSelectedCallback selectedCallback;
final BottomNavigationController controller;
BottomNavigation(this.tabItems,
{this.selectedPos = 0,
this.barHeight = 60,
this.barBackgroundColor = Colors.white,
this.circleSize = 58,
this.circleStrokeWidth = 4,
this.iconsSize = 32,
this.padding = 16,
this.selectedIconColor = Colors.white,
this.normalIconColor = Colors.deepPurpleAccent,
this.animationDuration = const Duration(milliseconds: 300),
this.selectedCallback,
this.controller})
: assert(tabItems != null && tabItems.length >= 2 && tabItems.length <= 5,
"tabItems is required");
#override
State<StatefulWidget> createState() => _BottomNavigationState();
}
class _BottomNavigationState extends State<BottomNavigation>
with TickerProviderStateMixin {
Curve _animationsCurve = Cubic(0.27, 1.21, .77, 1.09);
AnimationController itemsController;
Animation<double> selectedPosAnimation;
Animation<double> itemsAnimation;
List<double> _itemsSelectedState;
int selectedPos;
int previousSelectedPos;
BottomNavigationController _controller;
#override
void initState() {
super.initState();
if (widget.controller != null) {
_controller = widget.controller;
previousSelectedPos = selectedPos = _controller.value;
} else {
previousSelectedPos = selectedPos = widget.selectedPos;
_controller = BottomNavigationController(selectedPos);
}
_controller.addListener(_newSelectedPosNotify);
_itemsSelectedState = List.generate(widget.tabItems.length, (index) {
return selectedPos == index ? 1.0 : 0.0;
});
itemsController = new AnimationController(
vsync: this, duration: widget.animationDuration);
itemsController.addListener(() {
setState(() {
_itemsSelectedState.asMap().forEach((i, value) {
if (i == previousSelectedPos) {
_itemsSelectedState[previousSelectedPos] =
1.0 - itemsAnimation.value;
} else if (i == selectedPos) {
_itemsSelectedState[selectedPos] = itemsAnimation.value;
} else {
_itemsSelectedState[i] = 0.0;
}
});
});
});
selectedPosAnimation = makeSelectedPosAnimation(
selectedPos.toDouble(), selectedPos.toDouble());
itemsAnimation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: itemsController, curve: _animationsCurve));
}
Animation<double> makeSelectedPosAnimation(double begin, double end) {
return Tween(begin: begin, end: end).animate(
CurvedAnimation(parent: itemsController, curve: _animationsCurve));
}
void onSelectedPosAnimate() {
setState(() {});
}
void _newSelectedPosNotify() {
_setSelectedPos(widget.controller.value);
}
#override
Widget build(BuildContext context) {
double fullWidth = MediaQuery.of(context).size.width;
double fullHeight =
widget.barHeight + (widget.circleSize / 2) + widget.circleStrokeWidth;
double sectionsWidth = (fullWidth / 1.2) / widget.tabItems.length;
//Create the boxes Rect
List<Rect> boxes = List();
widget.tabItems.asMap().forEach((i, tabItem) {
double left = (i + 0.5) * sectionsWidth;
double top = fullHeight - widget.barHeight;
double right = left + sectionsWidth;
double bottom = fullHeight;
boxes.add(Rect.fromLTRB(left, top, right, bottom));
});
List<Widget> children = List();
// This is the full view transparent background (have free space for circle)
children.add(Padding(
padding: EdgeInsets.only(bottom: widget.padding),
child: Container(
width: fullWidth,
height: fullHeight,
)));
// This is the bar background (bottom section of our view)
children.add(Positioned.fill(
child: Padding(
padding: EdgeInsets.only(
left: widget.padding,
right: widget.padding,
bottom: widget.padding),
child: Container(
width: MediaQuery.of(context).size.width,
height: widget.barHeight,
decoration: BoxDecoration(
color: widget.barBackgroundColor,
borderRadius: BorderRadius.all(Radius.circular(20)),
boxShadow: [
new BoxShadow(color: Colors.black12, blurRadius: 8.0)
]),
),
),
top: fullHeight - widget.barHeight,
));
// This is the circle handle on selected
children.add(new Positioned(
child: Container(
width: widget.circleSize,
height: widget.circleSize,
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(14)),
color: widget.barBackgroundColor),
),
Container(
margin: EdgeInsets.all(widget.circleStrokeWidth),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(14)),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
CommonColor.primary,
widget.tabItems[selectedPos].circleColor,
CommonColor.slipBg,
])),
),
],
),
),
left: boxes[selectedPos].center.dx - (widget.circleSize / 2),
top: 0,
));
//Here are the Icons and texts of items
boxes.asMap().forEach((int pos, Rect r) {
// Icon
Color iconColor = pos == selectedPos
? widget.selectedIconColor
: widget.normalIconColor;
double scaleFactor = pos == selectedPos ? 1.2 : 1.0;
children.add(
Positioned(
child: Transform.scale(
scale: scaleFactor,
child: widget.tabItems[pos].asset != null
? SvgPicture.asset(widget.tabItems[pos].asset,
color: iconColor,
width: widget.iconsSize,
height: widget.iconsSize,
fit: BoxFit.cover)
: Icon(
widget.tabItems[pos].icon,
size: widget.iconsSize,
color: iconColor,
),
),
left: r.center.dx - (widget.iconsSize / 2),
top: r.center.dy -
(widget.iconsSize / 2) -
(_itemsSelectedState[pos] *
((widget.barHeight / 2) + widget.circleStrokeWidth)),
),
);
if (pos != selectedPos) {
children.add(Positioned.fromRect(
child: GestureDetector(
onTap: () {
_controller.value = pos;
},
),
rect: r,
));
}
});
return Stack(
children: children,
);
}
void _setSelectedPos(int pos) {
previousSelectedPos = selectedPos;
selectedPos = pos;
itemsController.forward(from: 0.0);
selectedPosAnimation = makeSelectedPosAnimation(
previousSelectedPos.toDouble(), selectedPos.toDouble());
selectedPosAnimation.addListener(onSelectedPosAnimate);
if (widget.selectedCallback != null) {
widget.selectedCallback(selectedPos);
}
}
#override
void dispose() {
super.dispose();
itemsController.dispose();
_controller.removeListener(_newSelectedPosNotify);
}
}
class BottomNavigationController extends ValueNotifier<int> {
BottomNavigationController(int value) : super(value);
}
You should use a FutureBuilder/ StreamBuilder to listen to an event after your api call is finished. Something like this:
Future<dynamic> yourApiCall() async {
// Execute your functions
return someValue;
}
Here in the UI, you can listen to the output of this function with:
FutureBuilder(
future: yourApiCall(),
builder: (context, snapshot) {
return BottomNavigation(
tabItems,
// ...other properties
selectedCallback: (int selectedPos) {
print("selected: $_selectedIndex");
if (snapshot.hasData) // Here you check if the snapshot.data is different from null. That means your api has finished and returned some value
_onNavigationBarItemTapped(selectedPos); // Only then, you notify the callback function
},
);
});

bottom navigation bar design in flutter expand an icon when it's clicked

I need a navigation bar on which I can expand an icon when it's clicked in FlutterBottom Navigation bar design image
i just edited bottomnavigationbar code
import 'package:flutter/material.dart';
import 'dart:collection' show Queue;
import 'dart:math' as math;
import 'package:flutter/widgets.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
enum BottomNavigationBarType {
fixed,
shifting,
}
class CoBottomNavigationBar extends StatefulWidget {
CoBottomNavigationBar({
Key key,
#required this.items,
this.onTap,
this.currentIndex = 0,
this.elevation = 8.0,
BottomNavigationBarType type,
Color fixedColor,
this.backgroundColor,
this.iconSize = 24.0,
Color selectedItemColor,
this.unselectedItemColor,
this.selectedFontSize = 14.0,
this.unselectedFontSize = 1,
this.showSelectedLabels = true,
bool showUnselectedLabels,
}) : assert(items != null),
assert(items.length >= 2),
assert(
items.every((BottomNavigationBarItem item) => item.title != null) ==
true,
'Every item must have a non-null title',
),
assert(0 <= currentIndex && currentIndex < items.length),
assert(elevation != null && elevation >= 0.0),
assert(iconSize != null && iconSize >= 0.0),
assert(selectedItemColor != null ? fixedColor == null : true,
'Either selectedItemColor or fixedColor can be specified, but not both'),
assert(selectedFontSize != null && selectedFontSize >= 0.0),
assert(unselectedFontSize != null && unselectedFontSize >= 0.0),
assert(showSelectedLabels != null),
type = _type(type, items),
selectedItemColor = selectedItemColor ?? fixedColor,
showUnselectedLabels =
showUnselectedLabels ?? _defaultShowUnselected(_type(type, items)),
super(key: key);
final List<BottomNavigationBarItem> items;
/// `setState` to rebuild the bottom navigation bar with the new [currentIndex].
final ValueChanged<int> onTap;
/// The index into [items] for the current active [BottomNavigationBarItem].
final int currentIndex;
/// The z-coordinate of this [BottomNavigationBar].
///
/// If null, defaults to `8.0`.
///
/// {#macro flutter.material.material.elevation}
final double elevation;
/// Defines the layout and behavior of a [BottomNavigationBar].
///
/// See documentation for [BottomNavigationBarType] for information on the
/// meaning of different types.
final BottomNavigationBarType type;
/// The value of [selectedItemColor].
///
/// This getter only exists for backwards compatibility, the
/// [selectedItemColor] property is preferred.
Color get fixedColor => selectedItemColor;
/// The color of the [BottomNavigationBar] itself.
///
/// If [type] is [BottomNavigationBarType.shifting] and the
/// [items]s, have [BottomNavigationBarItem.backgroundColor] set, the [item]'s
/// backgroundColor will splash and overwrite this color.
final Color backgroundColor;
/// The size of all of the [BottomNavigationBarItem] icons.
///
/// See [BottomNavigationBarItem.icon] for more information.
final double iconSize;
/// The color of the selected [BottomNavigationBarItem.icon] and
/// [BottomNavigationBarItem.label].
///
/// If null then the [ThemeData.primaryColor] is used.
final Color selectedItemColor;
/// The color of the unselected [BottomNavigationBarItem.icon] and
/// [BottomNavigationBarItem.label]s.
///
/// If null then the [TextTheme.caption]'s color is used.
final Color unselectedItemColor;
/// The font size of the [BottomNavigationBarItem] labels when they are selected.
///
/// Defaults to `14.0`.
final double selectedFontSize;
/// The font size of the [BottomNavigationBarItem] labels when they are not
/// selected.
///
/// Defaults to `12.0`.
final double unselectedFontSize;
/// Whether the labels are shown for the selected [BottomNavigationBarItem].
final bool showUnselectedLabels;
/// Whether the labels are shown for the unselected [BottomNavigationBarItem]s.
final bool showSelectedLabels;
// Used by the [BottomNavigationBar] constructor to set the [type] parameter.
//
// If type is provided, it is returned. Otherwise,
// [BottomNavigationBarType.fixed] is used for 3 or fewer items, and
// [BottomNavigationBarType.shifting] is used for 4+ items.
static BottomNavigationBarType _type(
BottomNavigationBarType type,
List<BottomNavigationBarItem> items,
) {
if (type != null) {
return type;
}
return items.length <= 3
? BottomNavigationBarType.fixed
: BottomNavigationBarType.shifting;
}
// Used by the [BottomNavigationBar] constructor to set the [showUnselected]
// parameter.
//
// Unselected labels are shown by default for [BottomNavigationBarType.fixed],
// and hidden by default for [BottomNavigationBarType.shifting].
static bool _defaultShowUnselected(BottomNavigationBarType type) {
switch (type) {
case BottomNavigationBarType.shifting:
return false;
case BottomNavigationBarType.fixed:
return true;
}
assert(false);
return false;
}
#override
_BottomNavigationBarState createState() => _BottomNavigationBarState();
}
// This represents a single tile in the bottom navigation bar. It is intended
// to go into a flex container.
class _BottomNavigationTile extends StatelessWidget {
const _BottomNavigationTile(
this.type,
this.item,
this.animation,
this.iconSize, {
this.onTap,
this.colorTween,
this.flex,
this.selected = false,
#required this.selectedFontSize,
#required this.unselectedFontSize,
this.showSelectedLabels,
this.showUnselectedLabels,
this.indexLabel,
}) : assert(type != null),
assert(item != null),
assert(animation != null),
assert(selected != null),
assert(selectedFontSize != null && selectedFontSize >= 0),
assert(unselectedFontSize != null && unselectedFontSize >= 0);
final BottomNavigationBarType type;
final BottomNavigationBarItem item;
final Animation<double> animation;
final double iconSize;
final VoidCallback onTap;
final ColorTween colorTween;
final double flex;
final bool selected;
final double selectedFontSize;
final double unselectedFontSize;
final String indexLabel;
final bool showSelectedLabels;
final bool showUnselectedLabels;
#override
Widget build(BuildContext context) {
// In order to use the flex container to grow the tile during animation, we
// need to divide the changes in flex allotment into smaller pieces to
// produce smooth animation. We do this by multiplying the flex value
// (which is an integer) by a large number.
int size;
double bottomPadding = 14;
double topPadding = selectedFontSize / 2.0;
if (showSelectedLabels && !showUnselectedLabels) {
bottomPadding = Tween<double>(
begin: 0.0,
end: 14,
).evaluate(animation);
topPadding = Tween<double>(
begin: selectedFontSize,
end: selectedFontSize / 2.0,
).evaluate(animation);
}
// Center all icons if no labels are shown.
if (!showSelectedLabels && !showUnselectedLabels) {
bottomPadding = 14;
topPadding = selectedFontSize;
}
switch (type) {
case BottomNavigationBarType.fixed:
size = 1;
break;
case BottomNavigationBarType.shifting:
size = (flex * 1000.0).round();
break;
}
Color _color=selected?item.backgroundColor:colorTween.evaluate(animation);
Widget _label1 = selected? _Label(
colorTween: _color,
animation: animation,
item: item,
selectedFontSize: selectedFontSize,
unselectedFontSize: unselectedFontSize,
showSelectedLabels: showSelectedLabels,
showUnselectedLabels: showUnselectedLabels,
):Text("");
BoxDecoration _mydecoration= selected ? BoxDecoration(
color: item.backgroundColor.withOpacity(0.5),
borderRadius: BorderRadius.all(Radius.circular(20)),):
BoxDecoration(color: Colors.white);
return Expanded(
flex: size,
child: Semantics(
container: true,
header: true,
selected: selected,
child: Stack(
children: <Widget>[
InkResponse(
onTap: onTap,
child: Container(
decoration: _mydecoration,
padding: EdgeInsets.only(top: 10,left: 10,right: 10,bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
_TileIcon(
colorTween: colorTween,
animation: animation,
iconSize: iconSize,
selected: selected,
item: item,
),
_label1,
],
),
),
),
Semantics(
label: indexLabel,
),
],
),
),
);
}
}
class _TileIcon extends StatelessWidget {
const _TileIcon({
Key key,
#required this.colorTween,
#required this.animation,
#required this.iconSize,
#required this.selected,
#required this.item,
}) : assert(selected != null),
assert(item != null),
super(key: key);
final ColorTween colorTween;
final Animation<double> animation;
final double iconSize;
final bool selected;
final BottomNavigationBarItem item;
#override
Widget build(BuildContext context) {
final Color iconColor = selected?item.backgroundColor:colorTween.evaluate(animation);
return Align(
alignment: Alignment.topCenter,
heightFactor: 1.0,
child: Container(
child: IconTheme(
data: IconThemeData(
color: iconColor,
size: iconSize,
),
child: selected ? item.activeIcon : item.icon,
),
),
);
}
}
class _Label extends StatelessWidget {
const _Label({
Key key,
#required this.colorTween,
#required this.animation,
#required this.item,
#required this.selectedFontSize,
#required this.unselectedFontSize,
#required this.showSelectedLabels,
#required this.showUnselectedLabels,
}) : assert(colorTween != null),
assert(animation != null),
assert(item != null),
assert(selectedFontSize != null),
assert(unselectedFontSize != null),
assert(showSelectedLabels != null),
assert(showUnselectedLabels != null),
super(key: key);
final Color colorTween;
final Animation<double> animation;
final BottomNavigationBarItem item;
final double selectedFontSize;
final double unselectedFontSize;
final bool showSelectedLabels;
final bool showUnselectedLabels;
#override
Widget build(BuildContext context) {
Widget text = DefaultTextStyle.merge(
style: TextStyle(
fontSize: selectedFontSize,
color: colorTween,
),
// The font size should grow here when active, but because of the way
// font rendering works, it doesn't grow smoothly if we just animate
// the font size, so we use a transform instead.
child: Transform(
transform: Matrix4.diagonal3(
Vector3.all(
Tween<double>(
begin: 0 / selectedFontSize,
end: 1.0,
).evaluate(animation),
),
),
alignment: Alignment.bottomCenter,
child: item.title,
),
);
if (!showUnselectedLabels && !showSelectedLabels) {
// Never show any labels.
text = Opacity(
alwaysIncludeSemantics: true,
opacity: 1.0,
child: text,
);
} else if (!showUnselectedLabels) {
// Fade selected labels in.
text = FadeTransition(
alwaysIncludeSemantics: true,
opacity: animation,
child: text,
);
} else if (!showSelectedLabels) {
// Fade selected labels out.
text = FadeTransition(
alwaysIncludeSemantics: true,
opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
child: text,
);
}
return Align(
alignment: Alignment.bottomCenter,
heightFactor: 1.0,
child: Container(child: text),
);
}
}
class _BottomNavigationBarState extends State<CoBottomNavigationBar>
with TickerProviderStateMixin {
List<AnimationController> _controllers = <AnimationController>[];
List<CurvedAnimation> _animations;
// A queue of color splashes currently being animated.
final Queue<_Circle> _circles = Queue<_Circle>();
// Last splash circle's color, and the final color of the control after
// animation is complete.
Color _backgroundColor;
static final Animatable<double> _flexTween =
Tween<double>(begin: 1.0, end: 1.5);
void _resetState() {
for (AnimationController controller in _controllers) controller.dispose();
for (_Circle circle in _circles) circle.dispose();
_circles.clear();
_controllers =
List<AnimationController>.generate(widget.items.length, (int index) {
return AnimationController(
duration: kThemeAnimationDuration,
vsync: this,
)..addListener(_rebuild);
});
_animations =
List<CurvedAnimation>.generate(widget.items.length, (int index) {
return CurvedAnimation(
parent: _controllers[index],
curve: Curves.fastOutSlowIn,
reverseCurve: Curves.fastOutSlowIn.flipped,
);
});
_controllers[widget.currentIndex].value = 1.0;
_backgroundColor = Colors.white;
}
#override
void initState() {
super.initState();
_resetState();
}
void _rebuild() {
setState(() {
// Rebuilding when any of the controllers tick, i.e. when the items are
// animated.
});
}
#override
void dispose() {
for (AnimationController controller in _controllers) controller.dispose();
for (_Circle circle in _circles) circle.dispose();
super.dispose();
}
double _evaluateFlex(Animation<double> animation) =>
_flexTween.evaluate(animation);
void _pushCircle(int index) {
if (widget.items[index].backgroundColor != null) {
_circles.add(
_Circle(
state: this,
index: index,
color: Colors.white,
vsync: this,
)..controller.addStatusListener(
(AnimationStatus status) {
switch (status) {
case AnimationStatus.completed:
setState(() {
final _Circle circle = _circles.removeFirst();
_backgroundColor = circle.color;
circle.dispose();
});
break;
case AnimationStatus.dismissed:
case AnimationStatus.forward:
case AnimationStatus.reverse:
break;
}
},
),
);
}
}
#override
void didUpdateWidget(CoBottomNavigationBar oldWidget) {
super.didUpdateWidget(oldWidget);
// No animated segue if the length of the items list changes.
if (widget.items.length != oldWidget.items.length) {
_resetState();
return;
}
if (widget.currentIndex != oldWidget.currentIndex) {
switch (widget.type) {
case BottomNavigationBarType.fixed:
break;
case BottomNavigationBarType.shifting:
_pushCircle(widget.currentIndex);
break;
}
_controllers[oldWidget.currentIndex].reverse();
_controllers[widget.currentIndex].forward();
} else {
if (_backgroundColor != widget.items[widget.currentIndex].backgroundColor)
_backgroundColor = Colors.white;
}
}
List<Widget> _createTiles() {
final MaterialLocalizations localizations =
MaterialLocalizations.of(context);
assert(localizations != null);
final ThemeData themeData = Theme.of(context);
Color themeColor;
switch (themeData.brightness) {
case Brightness.light:
themeColor = themeData.primaryColor;
break;
case Brightness.dark:
themeColor = themeData.accentColor;
break;
}
ColorTween colorTween;
switch (widget.type) {
case BottomNavigationBarType.fixed:
colorTween = ColorTween(
begin:
widget.unselectedItemColor ?? themeData.textTheme.caption.color,
end: widget.selectedItemColor ?? widget.fixedColor ?? themeColor,
);
break;
case BottomNavigationBarType.shifting:
colorTween = ColorTween(
begin: widget.unselectedItemColor ?? Colors.white,
end: widget.selectedItemColor ?? Colors.white,
);
break;
}
final List<Widget> tiles = <Widget>[];
for (int i = 0; i < widget.items.length; i++) {
tiles.add(_BottomNavigationTile(
widget.type,
widget.items[i],
_animations[i],
widget.iconSize,
selectedFontSize: widget.selectedFontSize,
unselectedFontSize: 1,
onTap: () {
if (widget.onTap != null) widget.onTap(i);
},
colorTween: colorTween,
flex: _evaluateFlex(_animations[i]),
selected: i == widget.currentIndex,
showSelectedLabels: widget.showSelectedLabels,
showUnselectedLabels: widget.showUnselectedLabels,
indexLabel: localizations.tabLabel(
tabIndex: i + 1, tabCount: widget.items.length),
));
}
return tiles;
}
Widget _createContainer(List<Widget> tiles) {
return DefaultTextStyle.merge(
overflow: TextOverflow.ellipsis,
child: Padding(
padding: EdgeInsets.only(left: 16, right: 16,top:16,bottom:16),
child: Row(
children: tiles,
),
),
);
}
#override
Widget build(BuildContext context) {
assert(debugCheckHasDirectionality(context));
assert(debugCheckHasMaterialLocalizations(context));
assert(debugCheckHasMediaQuery(context));
// Labels apply up to _bottomMargin padding. Remainder is media padding.
final double additionalBottomPadding = math.max(
MediaQuery.of(context).padding.bottom - widget.selectedFontSize / 2.0,
0.0);
Color backgroundColor;
switch (widget.type) {
case BottomNavigationBarType.fixed:
backgroundColor = widget.backgroundColor;
break;
case BottomNavigationBarType.shifting:
backgroundColor = Colors.white;
break;
}
return Semantics(
explicitChildNodes: true,
child: Material(
elevation: widget.elevation,
color: backgroundColor,
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
child: CustomPaint(
painter: _RadialPainter(
circles: _circles.toList(),
textDirection: Directionality.of(context),
),
child: Material(
// Splashes.
type: MaterialType.transparency,
child: MediaQuery.removePadding(
context: context,
removeBottom: true,
child: _createContainer(_createTiles()),
),
),
),
),
),
);
}
}
// Describes an animating color splash circle.
class _Circle {
_Circle({
#required this.state,
#required this.index,
#required this.color,
#required TickerProvider vsync,
}) : assert(state != null),
assert(index != null),
assert(color != null) {
controller = AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync,
);
animation = CurvedAnimation(
parent: controller,
curve: Curves.fastOutSlowIn,
);
controller.forward();
}
final _BottomNavigationBarState state;
final int index;
final Color color;
AnimationController controller;
CurvedAnimation animation;
double get horizontalLeadingOffset {
double weightSum(Iterable<Animation<double>> animations) {
// We're adding flex values instead of animation values to produce correct
// ratios.
return animations
.map<double>(state._evaluateFlex)
.fold<double>(0.0, (double sum, double value) => sum + value);
}
final double allWeights = weightSum(state._animations);
// These weights sum to the start edge of the indexed item.
final double leadingWeights =
weightSum(state._animations.sublist(0, index));
// Add half of its flex value in order to get to the center.
return (leadingWeights +
state._evaluateFlex(state._animations[index]) / 2.0) /
allWeights;
}
void dispose() {
controller.dispose();
}
}
// Paints the animating color splash circles.
class _RadialPainter extends CustomPainter {
_RadialPainter({
#required this.circles,
#required this.textDirection,
}) : assert(circles != null),
assert(textDirection != null);
final List<_Circle> circles;
final TextDirection textDirection;
// Computes the maximum radius attainable such that at least one of the
// bounding rectangle's corners touches the edge of the circle. Drawing a
// circle larger than this radius is not needed, since there is no perceivable
// difference within the cropped rectangle.
static double _maxRadius(Offset center, Size size) {
final double maxX = math.max(center.dx, size.width - center.dx);
final double maxY = math.max(center.dy, size.height - center.dy);
return math.sqrt(maxX * maxX + maxY * maxY);
}
#override
bool shouldRepaint(_RadialPainter oldPainter) {
if (textDirection != oldPainter.textDirection) return true;
if (circles == oldPainter.circles) return false;
if (circles.length != oldPainter.circles.length) return true;
for (int i = 0; i < circles.length; i += 1)
if (circles[i] != oldPainter.circles[i]) return true;
return false;
}
#override
void paint(Canvas canvas, Size size) {
for (_Circle circle in circles) {
final Paint paint = Paint()..color = circle.color;
final Rect rect = Rect.fromLTWH(0.0, 0.0, size.width, size.height);
canvas.clipRect(rect);
double leftFraction;
switch (textDirection) {
case TextDirection.rtl:
leftFraction = 1.0 - circle.horizontalLeadingOffset;
break;
case TextDirection.ltr:
leftFraction = circle.horizontalLeadingOffset;
break;
}
final Offset center =
Offset(leftFraction * size.width, size.height / 2.0);
final Tween<double> radiusTween = Tween<double>(
begin: 0.0,
end: _maxRadius(center, size),
);
canvas.drawCircle(
center,
radiusTween.transform(circle.animation.value),
paint,
);
}
}
}
and you can use it now as
bottomNavigationBar: CoBottomNavigationBar(
selectedFontSize: 16,
currentIndex: _selectedIndex,
selectedItemColor:Colors.yellow,
unselectedItemColor: Colors.black45,
onTap: _onItemTapped,
showUnselectedLabels: false,
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(
Icons.home,
),
title: Text(
' Home',
),
backgroundColor: Colors.red,
),
BottomNavigationBarItem(
icon: Icon(
Icons.thumb_up,
),
title: Text(
" Likes",
),
backgroundColor: Colors.blue,
),
BottomNavigationBarItem(
icon: Icon(
Icons.search,
),
title: Text(
' Search',
),
backgroundColor: Colors.pink,
),
BottomNavigationBarItem(
icon: Icon(
Icons.star,
),
title: Text(
" Profile",
),
backgroundColor: Colors.purple,
),
],
),