How to scroll to the next item in the ListView - flutter

I am trying to make a scrollable ListView to the next element so that it is always at the beginning (or center) of the page (as in PageView)
My problem is inertia, the backward movement of an element after scrolling.
How can I implements item's behavior without inertia?
Code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<ScrollController> _horizontalControllers;
ScrollController _verticalController;
var _itemCountHorizontal = 15;
bool _inProgress;
Orientation get isPortrait => MediaQuery.of(context).orientation;
double get _height => MediaQuery.of(context).size.height;
double get _width => MediaQuery.of(context).size.width;
double get horizontalPadding {
double _padd;
if (isPortrait == Orientation.portrait) {
_padd = _width * 0.01;
} else {
_padd = _width * 0.01;
}
return _padd;
}
double get verticalPadding {
double _padd;
if (isPortrait == Orientation.portrait) {
_padd = cardHeight * 0.005;
} else {
_padd = (_height - cardHeight) / 2;
}
return _padd;
}
double get cardHeight {
double cardH;
if (isPortrait == Orientation.portrait) {
cardH = cardWidth * 1.7;
} else {
cardH = _height * 0.9;
}
return cardH;
}
double get cardWidth {
var cardW = _width * 0.99;
if (cardW > _height / 1.7) {
cardW = _height / 1.77;
}
return cardW;
}
#override
void initState() {
_horizontalControllers = [
ScrollController(),
ScrollController(),
ScrollController(),
ScrollController(),
ScrollController(),
];
_verticalController = ScrollController();
_inProgress = false;
super.initState();
}
#override
void dispose() {
_horizontalControllers.forEach((element) {
element.dispose();
});
_verticalController.dispose();
super.dispose();
}
void _onEndScrollVertical(ScrollMetrics metrics) {
print("scroll before = ${metrics.extentBefore}");
print("scroll after = ${metrics.extentAfter}");
print("scroll inside = ${metrics.extentInside}");
print("index = ${metrics.axisDirection}");
print("item HEIGHT => $cardHeight");
final topPadd = MediaQuery.of(context).padding.top;
print('TOPPPPPPPPPP $topPadd');
/* int point = metrics.extentAfter ~/ (_height - topPadd);
var offset = (_height - topPadd) * point;
_inProgress = true;
Future.delayed(Duration(milliseconds: 100), () {
_verticalController.animateTo(offset,
duration: Duration(milliseconds: 1000), curve: Curves.fastOutSlowIn);
});
_inProgress = false;
*/
var halfOfTheHeight = cardHeight / 2;
var offsetOfItem = metrics.extentBefore % cardHeight;
if (offsetOfItem < halfOfTheHeight) {
final offset = metrics.extentBefore - offsetOfItem;
print("offsetOfItem1 = $offsetOfItem offset = $offset");
Future.delayed(Duration(milliseconds: 50), () {
_verticalController.animateTo(offset,
duration: Duration(milliseconds: 1000),
curve: Curves.fastOutSlowIn);
});
} else if (offsetOfItem > halfOfTheHeight) {
final offset = metrics.extentBefore + offsetOfItem;
print("offsetOfItem2 = $offsetOfItem offset = $offset");
Future.delayed(Duration(milliseconds: 50), () {
_verticalController.animateTo(offset,
duration: Duration(milliseconds: 1000),
curve: Curves.fastOutSlowIn);
});
}
}
void _onEndScrollHorizontal(ScrollMetrics metrics, int index) {
print("scroll before = ${metrics.extentBefore}");
print("scroll after = ${metrics.extentAfter}");
print("scroll inside = ${metrics.extentInside}");
print("item WIDTH => $cardWidth");
var halfOfTheWidth = _width / 2;
var offsetOfItem = metrics.extentBefore % _width;
if (offsetOfItem < halfOfTheWidth) {
final offset = metrics.extentBefore - offsetOfItem;
print("offsetOfItem1 = $offsetOfItem offset = $offset");
_inProgress = true;
Future.delayed(Duration(milliseconds: 10), () {
_horizontalControllers[index].animateTo(offset,
duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
});
_inProgress = false;
} else if (offsetOfItem > halfOfTheWidth) {
_inProgress = true;
final offset = metrics.extentBefore + offsetOfItem;
print("offsetOfItem2 = $offsetOfItem offset = $offset");
Future.delayed(Duration(milliseconds: 10), () {
_horizontalControllers[index].animateTo(offset,
duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
});
_inProgress = false;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification &&
scrollNotification.depth == 0) {
if (!_inProgress) {
print('ScrollEndNotification ===> $scrollNotification');
_onEndScrollVertical(scrollNotification.metrics);
}
}
return null;
},
child: buildListViewVertical(),
),
),
);
}
Widget buildListViewVertical() {
return ListView.builder(
itemCount: _itemCountHorizontal,
itemExtent: cardHeight,
controller: _verticalController,
itemBuilder: (BuildContext context, int index) {
return NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
if (scrollNotification is ScrollEndNotification &&
scrollNotification.depth == 0) {
print('ScrollEndNotification ===> $scrollNotification');
_onEndScrollHorizontal(scrollNotification.metrics, index);
}
return null;
},
child: buildListViewHorizontal(index));
},
);
}
Widget buildListViewHorizontal(int index) {
return ListView.builder(
controller: _horizontalControllers[index],
physics: ClampingScrollPhysics(),
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: _itemCountHorizontal + 1,
itemBuilder: (BuildContext context, int index) =>
index < _itemCountHorizontal
? Padding(
padding: EdgeInsets.only(
left: horizontalPadding,
right: horizontalPadding,
top: verticalPadding,
bottom: verticalPadding,
),
child: Container(height: 340, width: 200, color: Colors.red),
)
: SizedBox(
width: 50,
),
);
}
}
Here is a working example for DartPad
Update:
I add CustomScrollPhysics() to ListView, and that solution removed the inertia on reverse motions. Howewer, inertia persisted when moving from index 0 and above...
Code:
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
class CustomScrollPhysics extends ScrollPhysics {
const CustomScrollPhysics({ScrollPhysics parent}) : super(parent: parent);
#override
SpringDescription get spring => SpringDescription(damping: 0.1);
#override
CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
return CustomScrollPhysics(parent: buildParent(ancestor));
}
}

you can use DraggableScrollableSheet and SingleChildScrollView Widgets

first you need to change you widget into statefullwidget then you need to use Global key to scroll to the position that you want. and the position that you want to scroll to "widget ex." must have a name .

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

Disable remove from List when Swipe up

i am using this plugin for swiping. one of the flow of the app is to,
swipe up to view the user Profile.
but if i swipe up to view the profile it will remove the user from the list, which is not what i want...
i want it that, if i swipe up it should not remove from the list. i have try to disable swipe up but not navigating to profile page.
i have download the plugin to edit but i cant figure out where is removing from the list..
here is the plugin code
library flutter_tindercard;
import 'dart:math';
import 'package:flutter/material.dart';
enum TriggerDirection { none, right, left, up, down }
/// A Tinder-Like Widget.
class TinderSwapCard extends StatefulWidget {
final CardBuilder _cardBuilder;
final int _totalNum;
final int _stackNum;
final int _animDuration;
final double _swipeEdge;
final double _swipeEdgeVertical;
final bool _swipeUp;
final bool _swipeDown;
final bool _allowVerticalMovement;
final CardSwipeCompleteCallback? swipeCompleteCallback;
final CardDragUpdateCallback? swipeUpdateCallback;
final CardController? cardController;
final List<Size> _cardSizes = [];
final List<Alignment> _cardAligns = [];
#override
_TinderSwapCardState createState() => _TinderSwapCardState();
/// Constructor requires Card Widget Builder [cardBuilder] and
/// your card count [totalNum]
/// option includes:
/// stack orientation [orientation], number of card display
/// in same time [stackNum], [swipeEdge] is the edge to determine
/// action(recover or swipe) when you release your swiping card it is the
/// value of alignment, 0.0 means middle, so it need bigger than zero.
/// and size control params;
TinderSwapCard({
required CardBuilder cardBuilder,
required int totalNum,
AmassOrientation orientation = AmassOrientation.bottom,
int stackNum = 3,
int animDuration = 800,
double swipeEdge = 3.0,
double swipeEdgeVertical = 8.0,
bool swipeUp = false,
bool swipeDown = false,
double? maxWidth,
double? maxHeight,
double? minWidth,
double? minHeight,
bool allowVerticalMovement = true,
this.cardController,
this.swipeCompleteCallback,
this.swipeUpdateCallback,
}) : assert(stackNum > 1),
assert(swipeEdge > 0),
assert(swipeEdgeVertical > 0),
assert(maxWidth! > minWidth! && maxHeight! > minHeight!),
_cardBuilder = cardBuilder,
_totalNum = totalNum,
_stackNum = stackNum,
_animDuration = animDuration,
_swipeEdge = swipeEdge,
_swipeEdgeVertical = swipeEdgeVertical,
_swipeUp = swipeUp,
_swipeDown = swipeDown,
_allowVerticalMovement = allowVerticalMovement {
final widthGap = maxWidth! - minWidth!;
final heightGap = maxHeight! - minHeight!;
for (var i = 0; i < _stackNum; i++) {
_cardSizes.add(
Size(minWidth + (widthGap / _stackNum) * i,
minHeight + (heightGap / _stackNum) * i),
);
switch (orientation) {
case AmassOrientation.bottom:
_cardAligns.add(
Alignment(
0.0,
(0.5 / (_stackNum - 1)) * (stackNum - i),
),
);
break;
case AmassOrientation.top:
_cardAligns.add(
Alignment(
0.0,
(-0.5 / (_stackNum - 1)) * (stackNum - i),
),
);
break;
case AmassOrientation.left:
_cardAligns.add(
Alignment(
(-0.5 / (_stackNum - 1)) * (stackNum - i),
0.0,
),
);
break;
case AmassOrientation.right:
_cardAligns.add(
Alignment(
(0.5 / (_stackNum - 1)) * (stackNum - i),
0.0,
),
);
break;
}
}
}
}
class _TinderSwapCardState extends State<TinderSwapCard>
with TickerProviderStateMixin {
late Alignment frontCardAlign;
late AnimationController _animationController;
late int _currentFront;
static TriggerDirection? _trigger;
Widget _buildCard(BuildContext context, int realIndex) {
if (realIndex < 0) {
return Container();
}
final index = realIndex - _currentFront;
if (index == widget._stackNum - 1) {
return Align(
alignment: _animationController.status == AnimationStatus.forward
? frontCardAlign = CardAnimation.frontCardAlign(
_animationController,
frontCardAlign,
widget._cardAligns[widget._stackNum - 1],
widget._swipeEdge,
widget._swipeUp,
widget._swipeDown,
).value
: frontCardAlign,
child: Transform.rotate(
angle: (pi / 180.0) *
(_animationController.status == AnimationStatus.forward
? CardAnimation.frontCardRota(
_animationController, frontCardAlign.x)
.value
: frontCardAlign.x),
child: SizedBox.fromSize(
size: widget._cardSizes[index],
child: widget._cardBuilder(
context,
widget._totalNum - realIndex - 1,
),
),
),
);
}
return Align(
alignment: _animationController.status == AnimationStatus.forward &&
(frontCardAlign.x > 3.0 ||
frontCardAlign.x < -3.0 ||
frontCardAlign.y > 3 ||
frontCardAlign.y < -3)
? CardAnimation.backCardAlign(
_animationController,
widget._cardAligns[index],
widget._cardAligns[index + 1],
).value
: widget._cardAligns[index],
child: SizedBox.fromSize(
size: _animationController.status == AnimationStatus.forward &&
(frontCardAlign.x > 3.0 ||
frontCardAlign.x < -3.0 ||
frontCardAlign.y > 3 ||
frontCardAlign.y < -3)
? CardAnimation.backCardSize(
_animationController,
widget._cardSizes[index],
widget._cardSizes[index + 1],
).value
: widget._cardSizes[index],
child: widget._cardBuilder(
context,
widget._totalNum - realIndex - 1,
),
),
);
}
List<Widget> _buildCards(BuildContext context) {
final cards = <Widget>[];
for (var i = _currentFront; i < _currentFront + widget._stackNum; i++) {
cards.add(_buildCard(context, i));
}
cards.add(SizedBox.expand(
child: GestureDetector(
onPanUpdate: (final details) {
setState(() {
if (widget._allowVerticalMovement == true) {
frontCardAlign = Alignment(
frontCardAlign.x +
details.delta.dx * 20 / MediaQuery.of(context).size.width,
frontCardAlign.y +
details.delta.dy * 30 / MediaQuery.of(context).size.height,
);
} else {
frontCardAlign = Alignment(
frontCardAlign.x +
details.delta.dx * 20 / MediaQuery.of(context).size.width,
0,
);
if (widget.swipeUpdateCallback != null) {
widget.swipeUpdateCallback!(details, frontCardAlign);
}
}
if (widget.swipeUpdateCallback != null) {
widget.swipeUpdateCallback!(details, frontCardAlign);
}
});
},
onPanEnd: (final details) {
animateCards(TriggerDirection.none);
},
),
));
return cards;
}
void animateCards(TriggerDirection trigger) {
if (_animationController.isAnimating ||
_currentFront + widget._stackNum == 0) {
return;
}
_trigger = trigger;
_animationController.stop();
_animationController.value = 0.0;
_animationController.forward();
}
void triggerSwap(TriggerDirection trigger) {
animateCards(trigger);
}
// support for asynchronous data events
#override
void didUpdateWidget(covariant TinderSwapCard oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget._totalNum != oldWidget._totalNum) {
_initState();
}
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
void initState() {
super.initState();
_initState();
}
void _initState() {
_currentFront = widget._totalNum - widget._stackNum;
frontCardAlign = widget._cardAligns[widget._cardAligns.length - 1];
_animationController = AnimationController(
vsync: this,
duration: Duration(
milliseconds: widget._animDuration,
),
);
_animationController.addListener(() => setState(() {}));
_animationController.addStatusListener(
(final status) {
final index = widget._totalNum - widget._stackNum - _currentFront;
if (status == AnimationStatus.completed) {
CardSwipeOrientation orientation;
if (frontCardAlign.x < -widget._swipeEdge) {
orientation = CardSwipeOrientation.left;
} else if (frontCardAlign.x > widget._swipeEdge) {
orientation = CardSwipeOrientation.right;
} else if (frontCardAlign.y < -widget._swipeEdgeVertical) {
orientation = CardSwipeOrientation.up;
} else if (frontCardAlign.y > widget._swipeEdgeVertical) {
orientation = CardSwipeOrientation.down;
} else {
frontCardAlign = widget._cardAligns[widget._stackNum - 1];
orientation = CardSwipeOrientation.recover;
}
if (widget.swipeCompleteCallback != null) {
widget.swipeCompleteCallback!(orientation, index);
}
if (orientation != CardSwipeOrientation.recover) changeCardOrder();
}
},
);
}
#override
Widget build(BuildContext context) {
widget.cardController?.addListener(triggerSwap);
return Stack(children: _buildCards(context));
}
void changeCardOrder() {
setState(() {
_currentFront--;
frontCardAlign = widget._cardAligns[widget._stackNum - 1];
});
}
}
typedef CardBuilder = Widget Function(BuildContext context, int index);
enum CardSwipeOrientation { left, right, recover, up, down }
/// swipe card to [CardSwipeOrientation.left] or [CardSwipeOrientation.right]
/// , [CardSwipeOrientation.recover] means back to start.
typedef CardSwipeCompleteCallback = void Function(
CardSwipeOrientation orientation, int index);
/// [DragUpdateDetails] of swiping card.
typedef CardDragUpdateCallback = void Function(
DragUpdateDetails details, Alignment align);
enum AmassOrientation { top, bottom, left, right }
class CardAnimation {
static Animation<Alignment> frontCardAlign(
AnimationController controller,
Alignment beginAlign,
Alignment baseAlign,
double swipeEdge,
bool swipeUp,
bool swipeDown,
) {
double endX, endY;
if (_TinderSwapCardState._trigger == TriggerDirection.none) {
endX = beginAlign.x > 0
? (beginAlign.x > swipeEdge ? beginAlign.x + 10.0 : baseAlign.x)
: (beginAlign.x < -swipeEdge ? beginAlign.x - 10.0 : baseAlign.x);
endY = beginAlign.x > 3.0 || beginAlign.x < -swipeEdge
? beginAlign.y
: baseAlign.y;
if (swipeUp || swipeDown) {
if (beginAlign.y < 0) {
if (swipeUp) {
endY =
beginAlign.y < -swipeEdge ? beginAlign.y - 10.0 : baseAlign.y;
}
} else if (beginAlign.y > 0) {
if (swipeDown) {
endY = beginAlign.y > swipeEdge ? beginAlign.y + 10.0 : baseAlign.y;
}
}
}
} else if (_TinderSwapCardState._trigger == TriggerDirection.left) {
endX = beginAlign.x - swipeEdge;
endY = beginAlign.y + 0.5;
}
/* Trigger Swipe Up or Down */
else if (_TinderSwapCardState._trigger == TriggerDirection.up ||
_TinderSwapCardState._trigger == TriggerDirection.down) {
var beginY =
_TinderSwapCardState._trigger == TriggerDirection.up ? -10 : 10;
endY = beginY < -swipeEdge ? beginY - 10.0 : baseAlign.y;
endX = beginAlign.x > 0
? (beginAlign.x > swipeEdge ? beginAlign.x + 10.0 : baseAlign.x)
: (beginAlign.x < -swipeEdge ? beginAlign.x - 10.0 : baseAlign.x);
} else {
endX = beginAlign.x + swipeEdge;
endY = beginAlign.y + 0.5;
}
return AlignmentTween(
begin: beginAlign,
end: Alignment(endX, endY),
).animate(
CurvedAnimation(
parent: controller,
curve: Curves.easeOut,
),
);
}
static Animation<double> frontCardRota(
AnimationController controller, double beginRot) {
return Tween(begin: beginRot, end: 0.0).animate(
CurvedAnimation(
parent: controller,
curve: Curves.easeOut,
),
);
}
static Animation<Size?> backCardSize(
AnimationController controller,
Size beginSize,
Size endSize,
) {
return SizeTween(begin: beginSize, end: endSize).animate(
CurvedAnimation(
parent: controller,
curve: Curves.easeOut,
),
);
}
static Animation<Alignment> backCardAlign(
AnimationController controller,
Alignment beginAlign,
Alignment endAlign,
) {
return AlignmentTween(begin: beginAlign, end: endAlign).animate(
CurvedAnimation(
parent: controller,
curve: Curves.easeOut,
),
);
}
}
typedef TriggerListener = void Function(TriggerDirection trigger);
class CardController {
late TriggerListener? _listener;
void triggerLeft() {
if (_listener != null) {
_listener!(TriggerDirection.left);
}
}
void triggerRight() {
if (_listener != null) {
_listener!(TriggerDirection.right);
}
}
void triggerUp() {
if (_listener != null) {
_listener!(TriggerDirection.up);
}
}
void triggerDown() {
if (_listener != null) {
_listener!(TriggerDirection.down);
}
}
// ignore: use_setters_to_change_properties
void addListener(final TriggerListener listener) {
_listener = listener;
}
void removeListener() {
_listener = null;
}
}
i pass in the current index to swipeUpdateCallback then set SwipeUp to false...
here is the full sample code
import 'dart:async';
import 'dart:math';
import 'package:card/card.dart';
import 'package:card/up.dart';
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
late StreamController<List<String>> _streamController;
class _HomePageState extends State<HomePage> {
List<String> welcomeImages = [
"assets/welcome0.png",
"assets/welcome1.png",
"assets/welcome2.png",
];
#override
initState() {
_streamController = StreamController<List<String>>();
super.initState();
}
void _addToStream() {
Random random = Random();
int index = random.nextInt(3);
welcomeImages.add('assets/welcome$index.png');
_streamController.add(welcomeImages);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("asynchronous data events demo"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Added image appears on top:',
),
StreamBuilder<List<String>>(
stream: _streamController.stream,
initialData: welcomeImages,
builder:
(BuildContext context, AsyncSnapshot<List<String>> snapshot) {
print('snapshot.data.length: ${snapshot.data!.length}');
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('Add image');
case ConnectionState.waiting:
case ConnectionState.active:
return _asyncDataExample(
context,
snapshot.data!,
(CardSwipeOrientation orientation) {
// welcomeImages[0] is the swiped card
// you can send to backend service in here
welcomeImages.removeAt(0);
_streamController.add(welcomeImages);
},
);
case ConnectionState.done:
return Text('\$${snapshot.data} (closed)');
}
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _addToStream,
tooltip: 'Add image',
child: Icon(Icons.add),
),
);
}
Widget _asyncDataExample(
BuildContext context, List<String> imageList, Function onSwipe) {
CardController controller; //Use this to trigger swap.
return Center(
child: Container(
height: MediaQuery.of(context).size.height * 0.6,
child: TinderSwapCard(
orientation: AmassOrientation.bottom,
totalNum: imageList.length,
stackNum: 4,
swipeEdge: 4.0,
maxWidth: MediaQuery.of(context).size.width * 0.9,
maxHeight: MediaQuery.of(context).size.width * 0.9,
minWidth: MediaQuery.of(context).size.width * 0.8,
minHeight: MediaQuery.of(context).size.width * 0.8,
cardBuilder: (context, index) => Card(
child: Image.asset('${imageList[index]}'),
),
cardController: controller = CardController(),
swipeUpdateCallback:
(DragUpdateDetails details, Alignment align, int index) {
/// Get swiping card's alignment
if (align.y < -10) {
Navigator.push(
context, MaterialPageRoute(builder: (context) => SwipeUp()));
}
},
swipeCompleteCallback: (CardSwipeOrientation orientation, int index) {
if (orientation != CardSwipeOrientation.recover) {
onSwipe(orientation);
}
if (orientation == CardSwipeOrientation.up) {}
},
),
),
);
}
}

Flutter: How to show AppBar only after scrolling?

That's what you read. I don't want to hide AppBar when scrolling, there's a lot of info on that.
What I want is the exact opposite. I want my homepage to open with no AppBar and then, when the user starts scrolling, the appbar will be displayed.
This website does exactly what I want to reproduce: https://www.kirschnerbrasil.cc/ (in the desktop version).
I guess I need do use the SliverAppBar, but I haven't manage to do so yet. Can anyone help?
Thanks!
In that case, you will have to make your custom widget the appbar. Please have a look into below code, it will help you to understand the procedure:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// Height of your Container
static final _containerHeight = 100.0;
// You don't need to change any of these variables
var _fromTop = -_containerHeight;
var _controller = ScrollController();
var _allowReverse = true, _allowForward = true;
var _prevOffset = 0.0;
var _prevForwardOffset = -_containerHeight;
var _prevReverseOffset = 0.0;
#override
void initState() {
super.initState();
_controller.addListener(_listener);
}
// entire logic is inside this listener for ListView
void _listener() {
double offset = _controller.offset;
var direction = _controller.position.userScrollDirection;
if (direction == ScrollDirection.reverse) {
_allowForward = true;
if (_allowReverse) {
_allowReverse = false;
_prevOffset = offset;
_prevForwardOffset = _fromTop;
}
var difference = offset - _prevOffset;
_fromTop = _prevForwardOffset + difference;
if (_fromTop > 0) _fromTop = 0;
} else if (direction == ScrollDirection.forward) {
_allowReverse = true;
if (_allowForward) {
_allowForward = false;
_prevOffset = offset;
_prevReverseOffset = _fromTop;
}
var difference = offset - _prevOffset;
_fromTop = _prevReverseOffset + difference;
if (_fromTop < -_containerHeight) _fromTop = -_containerHeight;
}
setState(() {}); // for simplicity I'm calling setState here, you can put bool values to only call setState when there is a genuine change in _fromTop
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("ListView")),
body: Stack(
children: <Widget>[
_yourListView(),
Positioned(
top: _fromTop,
left: 0,
right: 0,
child: _yourContainer(),
)
],
),
);
}
Widget _yourListView() {
return ListView.builder(
itemCount: 100,
controller: _controller,
itemBuilder: (_, index) => ListTile(title: Text("Item $index")),
);
}
Widget _yourContainer() {
return Opacity(
opacity: 1 - (-_fromTop / _containerHeight),
child: Container(
height: _containerHeight,
color: Colors.red,
alignment: Alignment.center,
child: Text("Your Container", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white)),
),
);
}
}
Heres the code i edit from #Gourango Sutradhar answer, if you want the top to disappear only when it reach the top
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// Height of your Container
static final _containerHeight = 100.0;
// You don't need to change any of these variables
var _fromTop = -_containerHeight;
var _controller = ScrollController();
var _allowReverse = true, _allowForward = true;
var _prevOffset = 0.0;
var _prevForwardOffset = -_containerHeight;
var _prevReverseOffset = 0.0;
#override
void initState() {
super.initState();
_controller.addListener(_listener);
}
// entire logic is inside this listener for ListView
void _listener() {
double offset = _controller.offset;
var direction = _controller.position.userScrollDirection;
if (direction == ScrollDirection.reverse) {
_allowForward = true;
if (_allowReverse) {
_allowReverse = false;
_prevOffset = offset;
_prevForwardOffset = _fromTop;
}
var difference = offset - _prevOffset;
_fromTop = _prevForwardOffset + difference;
if (_fromTop > 0) _fromTop = 0;
} else if (direction == ScrollDirection.forward) {
_allowReverse = true;
if (_allowForward) {
_allowForward = false;
_prevReverseOffset = _fromTop;
}
var difference = offset - _prevOffset;
if (offset > 100.0) {
_prevOffset = offset;
}
if (offset < 100.0) {
_fromTop = _prevReverseOffset + difference;
if (_fromTop < -_containerHeight) _fromTop = -_containerHeight;
}
}
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("ListView")),
body: Stack(
children: <Widget>[
_yourListView(),
Positioned(
top: _fromTop,
left: 0,
right: 0,
child: _yourContainer(),
)
],
),
);
}
Widget _yourListView() {
return ListView.builder(
itemCount: 100,
controller: _controller,
itemBuilder: (_, index) => ListTile(title: Text("Item $index")),
);
}
Widget _yourContainer() {
return Opacity(
opacity: 1,
child: Container(
height: _containerHeight,
color: Colors.red,
alignment: Alignment.center,
child: Text("Your Container", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white)),
),
);
}
}

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
},
);
});