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