Scroll To Top on Button Click in Flutter - flutter

I'm new to Flutter, I'm facing this issue,
There is a button below in ScrollController, And when I click it. It should scroll To top.
ScrollController is embedded in SingleChildScrollView
Widget build(BuildContext context) {
return Container(
child: SingleChildScrollView(
controller: scrollController)
);
}
I tried using
setState(() {
scrollController.animateTo(0.0,
duration: const Duration(milliseconds: 300),
curve: Curves.ease);
}
Below is the Whole Code
Future<void> nextPressed() async {
final bool connectivity = await ConnectivityManager().isInternetAvailable();
if(connectivity) {
final FormState form = _formKey.currentState;
if (form.validate()) {
form.save();
updateDOBError(_dobController.text);
setState(() {
if(dobErrorString.isEmpty){
_errorAlertVisibilityDOB = false;
}else{
_errorAlertVisibilityDOB = true;
}
_errorAlertVisibility = true;
});
} else {
updateDOBError(_dobController.text);
setState(() {
if(dobErrorString.isEmpty){
_errorAlertVisibilityDOB = false;
}else{
_errorAlertVisibilityDOB = true;
}
errorType = RegistrationErrorType.validationError;
_errorAlertVisibility = true;
});
--> scrollController.animateTo(0.0,
duration: const Duration(milliseconds: 300), curve: Curves.ease);
}
}else{
const SnackBar snackBar =
SnackBar(content: Text(AppStrings.no_internet));
Scaffold.of(context).showSnackBar(snackBar);
}}
The --> is the place, I'm facing issue
Required Result
Issue to be Fixed

Use following on button clicked
setState((){
_scrollController.animateTo(
_scrollController.position.minScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 500),
);})

Related

How to pass parameter functions to widget stored within a map<int, widget>?

Ive been trying to work this out for awhile now still getting a hang of flutter as its fairly new to me. Im trying to create an animated menu where once i click on the hamburger icon, it opens the menu and when choosing options within the menu, the screen changes. Im having an issue with passing the callback function for each screen to change menuOpen to true using setState. Since I cannot add the callback function to the screen when adding it to the map, what would i need to do in order to send a function as a parameter to change the menuOpen value to open or close the menu. When i add the call back function in the initialisation it says The instance member 'setState' can't be accessed in an initializer. Try replacing the reference to the instance member with a different expression
BuildPageStack builds the screen based on the current index of the map.
Widget buildPageStack(int place, List<Widget> pageList) {
final deviceWidth = MediaQuery.of(context).size.width;
return AnimatedPositioned(
duration: duration,
top: menuOpen ? deviceWidth * 0.10 : 0.0,
bottom: 0,
left: menuOpen ? deviceWidth * 0.40 - (place * 30) : 0.0,
right: menuOpen ? deviceWidth * -0.130 + (place * 30) : 0.0,
child: ScaleTransition(
scale: scaleAnimations[place],
child: GestureDetector(
onTap: () {
if (!mounted)
return;
else if (menuOpen) {
setState(() {
menuOpen = false;
animationController.reverse();
});
}
},
child: AbsorbPointer(
absorbing: menuOpen,
child: Stack(
children: [
Material(
animationDuration: duration,
borderRadius: BorderRadius.circular(menuOpen ? 20.0 : 0.0),
child: pageList[place]),
// Container(
// color: Theme.of(context).primaryColor.withOpacity(0.5),
// ),
],
),
),
),
),
);
}
initState method
void initState() {
super.initState();
animationController = AnimationController(vsync: this, duration: duration);
scaleAnimations = [
Tween<double>(begin: 1.0, end: 0.8).animate(animationController),
Tween<double>(begin: 1.0, end: 0.7).animate(animationController),
Tween<double>(begin: 1.0, end: 0.6).animate(animationController),
];
animationController.forward();
adminScreenSnapshot = adminpages.values.toList();
userScreenSnapshot = userpages.values.toList();
}
Map initialization and call, if the user is an admin it shows 3 screens, if user it shows 2
Map<int, Widget> adminpages = {
0: Main_Foster(menuCallback: (){
setState(() {
menuOpen = true;
animationController.forward();
});}),
1: ProfileScreen(menuCallback: (){
setState(() {
menuOpen = true;
animationController.forward();
});}),
2: CMS(menuCallback: (){
setState(() {
menuOpen = true;
animationController.forward();
});}),
};
Map<int, Widget> userpages = {
0: Main_Foster(menuCallback: (){
setState(() {
menuOpen = true;
animationController.forward();
});}),
1: ProfileScreen(menuCallback: () {
setState(() {
menuOpen = true;
animationController.forward();
});}),
};
List<Widget> adminScreenSnapshot;
List<Widget> userScreenSnapshot;
List<Widget> finalPageStack() {
List<Widget> returnStack = [];
returnStack.add(MenuScreen(
menuCallback: (selectedIndex) {
if (widget.isAdmin) {
setState(() {
adminScreenSnapshot = adminpages.values.toList();
final selectedWidget = adminScreenSnapshot.removeAt(selectedIndex);
adminScreenSnapshot.insert(0, selectedWidget);
});
} else {
setState(() {
userScreenSnapshot = userpages.values.toList();
final selectedWidget = userScreenSnapshot.removeAt(selectedIndex);
userScreenSnapshot.insert(0, selectedWidget);
});
}
},
));
if (widget.isAdmin) {
adminScreenSnapshot
.asMap()
.entries
.map((screen) => buildPageStack(screen.key, adminScreenSnapshot))
.toList()
.reversed
..forEach((screen) {
returnStack.add(screen);
});
} else {
userScreenSnapshot
.asMap()
.entries
.map((screen) => buildPageStack(screen.key, userScreenSnapshot))
.toList()
.reversed
..forEach((screen) {
returnStack.add(screen);
});
}
return returnStack;
}
FinalPageStack returns the required stack
List<Widget> finalPageStack() {
List<Widget> returnStack = [];
returnStack.add(MenuScreen(
menuCallback: (selectedIndex) {
if (widget.isAdmin) {
setState(() {
adminScreenSnapshot = adminpages.values.toList();
final selectedWidget = adminScreenSnapshot.removeAt(selectedIndex);
adminScreenSnapshot.insert(0, selectedWidget);
});
} else {
setState(() {
userScreenSnapshot = userpages.values.toList();
final selectedWidget = userScreenSnapshot.removeAt(selectedIndex);
userScreenSnapshot.insert(0, selectedWidget);
});
}
},
));
if (widget.isAdmin) {
adminScreenSnapshot
.asMap()
.entries
.map((screen) => buildPageStack(screen.key, adminScreenSnapshot))
.toList()
.reversed
..forEach((screen) {
returnStack.add(screen);
});
} else {
userScreenSnapshot
.asMap()
.entries
.map((screen) => buildPageStack(screen.key, userScreenSnapshot))
.toList()
.reversed
..forEach((screen) {
returnStack.add(screen);
});
}
return returnStack;
}

AnimationController repeat with curve animation

How do I make a AnimatedController that repeats, but starts up with a curve animation.
Code:
AnimationController :
var _animating = false;
AnimationController _rotationAnimationController;
Animation<double> _animation;
#override
void initState() {
super.initState();
_rotationAnimationController = AnimationController(
duration: Duration(milliseconds: 2000),
vsync: this,
);
_animation =
Tween<double>(begin: 0, end: 4 * pi ).animate(_rotationAnimationController)
..addListener(() {
setState(() {});
});
}
#override
void dispose() {
_rotationAnimationController.dispose();
super.dispose();
}
Button :
GestureDetector(
onTap: () {
Duration startStopTime = Duration(seconds: 3);
if(_animating) {
_rotationAnimationController.animateBack(1, duration: startStopTime, curve: Curves.easeOut);
setState(() {});
} else {
_rotationAnimationController.repeat() //This needs to start with a curve
}
setState(() {
_animating = !_animating;
});
},
child: [...]
),
If you could also make it so that when the repeat stops, it does that with a curve, that would be amazing too :)
Thanks for the help allready :D
The best I understood your question , you want a CurvedAnimation right ? This means your animation will repeat but follow a specific curve . So here the best I could do for you :
Define your AnimationController like this :
Animation<double> _animation;
AnimationController _animationController;
#override
void initState() {
super.initState();
_animationController =
AnimationController(vsync: this, duration: Duration(seconds: 2))
..addListener(() {
setState(() {});
});
final Animation curve =
CurvedAnimation(parent: _animationController, curve: Curves.easeOut);
_animation = Tween<double>(begin: 0, end: pi * 4).animate(curve);
}
and your GestureDetector like this :
GestureDetector(
onTap: () {
if (_animationController.isAnimating)
{
_animationController.animateBack(0,duration: Duration(seconds: 2), curve: Curves.easeIn);
}
else {
_animationController.repeat();
}
},
child: [...]
),
Edit :
I used a TweenAnimationBuilder to have the effect you want :
import 'package:flutter/material.dart';
import 'dart:math' as math;
class TweenAnimatedBuilderRotate extends StatefulWidget {
TweenAnimatedBuilderRotate({Key key}) : super(key: key);
#override
TweenAnimatedBuilderRotateState createState() =>
TweenAnimatedBuilderRotateState();
}
class TweenAnimatedBuilderRotateState
extends State<TweenAnimatedBuilderRotate> {
double _newAngle = 0;
Curve curveThatChanges = Curves.easeIn;
bool isAnimating = false;
int _millsecs = 2000;
void onCompletion() {
if (isAnimating) {
_newAngle += 4 * math.pi;
curveThatChanges = Curves.linear;
_millsecs = 1000;
setState(() {});
} else {
_newAngle = 0;
_millsecs = 2000;
}
}
void onContainerTap() {
if (isAnimating) {
isAnimating = false;
_newAngle = _newAngle;
setState(() {});
} else {
curveThatChanges = Curves.easeIn;
_newAngle += 4 * math.pi;
isAnimating = true;
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return TweenAnimationBuilder(
tween: Tween<double>(begin: 0, end: _newAngle),
duration: Duration(milliseconds: _millsecs),
onEnd: () => onCompletion(),
curve: curveThatChanges,
builder: (
BuildContext ctx,
double angle,
Widget child,
) {
_newAngle = angle;
return Center(
child: Transform(
transform: Matrix4.identity()..rotateZ(_newAngle),
alignment: FractionalOffset.center,
child: GestureDetector(
child: Container(
color: Colors.blueGrey,
width: 200,
height: 200,
),
onTap: () => onContainerTap(),
),
),
);
});
}
}
You can refer to this Medium article to understand about how TweenAnimationdBuilder Works. You can also modify _millsecs variable to speed up/down the animation. Pass TweenAnimatedBuilderRotate() in the body parameter of the Scaffold(...).

How to scroll flutter web app using arrow keys

I just build and deployed a flutter web app. The problem I encountered is that it doesn't scroll when I press arrow keys, also there is no scroll bar. (Only 2 figure gesture scrolling is possible)
I'm using SingleChildScrollView() with the column as its child.
Is there a way to implement them?
Or just one of them?
The code from Karan works, but when the app is in Debug Mode, instead of using the event.logicalKey.debugName == "Arrow Up", we could use event.logicalKey == LogicalKeyboardKey.arrowUp which works in both the debug and release mode.
class _MyKeyboardScrollingPageState extends State<MyKeyboardScrollingPage> {
final ScrollController _controller = ScrollController();
final FocusNode _focusNode = FocusNode();
void _handleKeyEvent(RawKeyEvent event) {
var offset = _controller.offset;
if (event.logicalKey == LogicalKeyboardKey.arrowUp) {
setState(() {
if (kReleaseMode) {
_controller.animateTo(offset - 200, duration: Duration(milliseconds: 30), curve: Curves.ease);
} else {
_controller.animateTo(offset - 200, duration: Duration(milliseconds: 30), curve: Curves.ease);
}
});
}
else if (event.logicalKey == LogicalKeyboardKey.arrowDown) {
setState(() {
if (kReleaseMode) {
_controller.animateTo(offset + 200, duration: Duration(milliseconds: 30), curve: Curves.ease);
} else {
_controller.animateTo(offset + 200, duration: Duration(milliseconds: 30), curve: Curves.ease);
}
});
}
}
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: RawKeyboardListener(
autoFocus = true,
focusNode = _focusNode,
onKey: _handleKeyEvent,
child: SingleChildScrollView(
controller: _controller,
child: SomeAwesomeWidget(),
),
),
);
}
}
I found one solution ...
Hope this helps someone with the same issue...
Using RawKeyboardListener(), we can listen to any keyboard stroke.
class _MyHomePageState extends State<MyHomePage> {
final ScrollController _controller = ScrollController();
final FocusNode _focusNode = FocusNode()
#override
void dispose() {
_focusNode.dispose();
super.dispose();
}
void _handleKeyEvent(RawKeyEvent event) {
var offset = _controller.offset; //Getting current position
if (event.logicalKey.debugName == "Arrow Down") {
setState(() {
if (kReleaseMode) {
//This block only runs when the application was compiled in release mode.
_controller.animateTo(offset + 50,
duration: Duration(milliseconds: 200), curve: Curves.ease);
} else {
// This will only print useful information in debug mode.
// print(_controller.position); to get information..
_controller.animateTo(offset + 50,
duration: Duration(milliseconds: 200), curve: Curves.ease);
}
});
} else if (event.logicalKey.debugName == "Arrow Up"){
setState(() {
if (kReleaseMode) {
_controller.animateTo(offset - 50,
duration: Duration(milliseconds: 200), curve: Curves.ease);
} else {
_controller.animateTo(offset - 50,
duration: Duration(milliseconds: 200), curve: Curves.ease);
}
});
#override
Widget build(BuildContext context) {
return Scaffold(
body: RawKeyboardListener(
autofocus: true,
focusNode: _focusNode,
onKey: _handleKeyEvent,
child: SingleChildScrollView(
controller: _controller,
child:...
}
}
You can wrap ScrollBar to SingleChildScrollView to show scroll bar, like this:
Scrollbar(
child: SingleChildScrollView(
child: Container(),
));
For the answers that are mentioned above to work, you need the following imports:
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
The Most simplest way to Scroll using mouse or Keyboard Arrow keys on Flutter Web is
ListView(
primary: true,
scrollDirection: Axis.vertical,
children:
No need to pass any ScrollController

scrollController.animateTo Invoked Multiple Times Asyncronously

I Have RawKeyboardListener that invoke _getIDCardInfo each time KeyDown is press and hold.
the problem is _getIDCardInfo is invoked multiple times. this make problem because scrollcontroller.animateTo is animated only in the last time _getIDCardInfo is invoked.
I want scrollcontroller.animateTo is animated each time _getIDCardInfo is invoked, not only in the last call of _getIDCardInfo.
how can i make _getIdCardInfo() is invoked syncronously. or there is any way scrollController can be animated properly without getting invoked multiple times.
_getIdCardInfo() async {
try {
final String result = await platform.invokeMethod('scanIDCard');
await _myItems.insert(0, {"id": result});
await _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
curve: Curves.easeIn,
duration: Duration(milliseconds: 30));
await _scrollController.animateTo(0,
curve: Curves.easeIn, duration: Duration(milliseconds: 50));
} on PlatformException catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
FocusNode _focusNode = FocusNode();
return RawKeyboardListener(
focusNode: _focusNode,
autofocus: true,
onKey: (RawKeyEvent event) async {
if ((event.runtimeType == RawKeyDownEvent &&
event.logicalKey.keyId == 1108101562648)) {
await _getIdCardInfo();
}
},
);
}
}
I guess what you need is to run animation recursively like this:
final _scrollController = ScrollController();
final _focusNode = FocusNode();
int _animateTimes = 0; // How many times we need to run an animation
Future<void> _animateFuture; // Is animation currently running
Future<void> _getIdCardInfo() async {
await _scrollController.animateTo(
Random().nextInt(1000).toDouble(),
duration: Duration(milliseconds: 1000),
curve: Curves.linear
);
_runNext();
}
void _runAnimation() {
_animateTimes++;
if (_animateFuture == null) {
_animateFuture = _getIdCardInfo();
}
}
void _runNext() {
_animateTimes--;
_animateFuture = null;
if (_animateTimes > 0) {
_animateFuture = _getIdCardInfo();
}
}
#override
void dispose() {
_focusNode.dispose(); // Don't forget to dispose of focus node
super.dispose();
}
...
RawKeyboardListener(
focusNode: _focusNode,
autofocus: true,
onKey: (RawKeyEvent event) async {
if (event is RawKeyDownEvent && event.logicalKey.keyId == 1108101562648) {
_runAnimation();
}
},
child: Text('asd')
),
...

Slide child widget in Row layout container from its current position

I have a Row that consists x number of Button widgets. So when user clicks on one of the Button I need to fade out the other buttons and the slide the clicked button to the starting position(0,0) of the Row layout. I managed to get the fade out animation to work.
final _opacityTween = Tween<double>(begin: 1, end: 0);
_opacityAnimation = _opacityTween.animate(CurvedAnimation(
parent: _controller,
curve: new Interval(0.00, 0.50, curve: Curves.linear)
// curve: Curves.ease
));
// And using the opacityAnimation value in Opacity widget.
return Opacity(
opacity: _opacityAnimation.value,
child: child,
);
However I have been unsuccessful to using Animation classes and widgets to slide the child widget to the start position of the Row. Basically I am not sure what kind of classes could be useful to achieve such animation. I can use the Tween and set the end position to (0, 0) and use Translation to achieve it, however I am not sure how to set the begin position of the Animation when I initialise it as the position of a child is not known.
Try this solution. I thing it is almost what you need. You just have to add SizeTransition widget.
Ask if you have any question.
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
int _pressedButton;
Animation<double> _animation;
AnimationController _controller;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animation = Tween<double>(begin: 1.0, end: 0.0)
.animate(_controller)
..addListener(() {
setState(() { });
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: <Widget>[
Row(
children: <Widget>[
_animatedButton(1),
_animatedButton(2),
_animatedButton(3),
_animatedButton(4)
]
),
Text(_pressedButton != null ? "$_pressedButton button pressed" : "No button pressed"),
RaisedButton(
child: Text("Reset"),
onPressed: () {
_controller.reset();
}
)
]
)
);
}
Widget _animatedButton(int button) {
if (button != _pressedButton) {
return SizeTransition(
sizeFactor: _animation,
axis: Axis.horizontal,
child: Opacity(
opacity: _animation.value,
child: _button(button)
)
);
} else {
return _button(button);
}
}
Widget _button(int button) {
return RaisedButton(
onPressed: () => onButtonClick(button),
child: Text("Button $button")
);
}
void onButtonClick(int button) {
setState(() {
_pressedButton = button;
});
_controller.forward();
}
}
UPDATE 1
Check one more way to do what you need. There are few different things in code below.
Change SingleTickerProviderStateMixin to TickerProviderStateMixin.
Use two animation controllers and animations (for fading out of unselected buttons and sliding left selected button).
Run animations sequentially (see animation tweens listeners).
Add bool _buttonSelected to track completion of animations.
Use _buttonSelected to build correct widget (Widget _buttonsWidget())
class _MyHomePageState extends State<MyHomePage> with TickerProviderStateMixin {
int _pressedButton = -1;
bool _buttonSelected = false;
Animation<double> _animationFadeOut;
AnimationController _controllerFadeOut;
Animation<double> _animationSlideLeft;
AnimationController _controllerSlideLeft;
#override
void initState() {
super.initState();
_initFadeOutAnimation();
_initSlideLeftAnimation();
}
void _initFadeOutAnimation() {
_controllerFadeOut = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animationFadeOut = Tween<double>(begin: 1.0, end: 0.0)
.animate(_controllerFadeOut)
..addListener(() {
setState(() {
if (_controllerFadeOut.isCompleted && !_controllerSlideLeft.isAnimating) {
_controllerSlideLeft.forward();
}
});
});
}
void _initSlideLeftAnimation() {
_controllerSlideLeft = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animationSlideLeft = Tween<double>(begin: 1.0, end: 0.0)
.animate(_controllerSlideLeft)
..addListener(() {
setState(() {
if (_controllerSlideLeft.isCompleted) {
_buttonSelected = true;
}
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Test"),
),
body: Column(
children: <Widget>[
_buttonsWidget(),
Text(_pressedButton != null ? "$_pressedButton button pressed" : "No button pressed"),
RaisedButton(
child: Text("Reset"),
onPressed: () {
_controllerFadeOut.reset();
_controllerSlideLeft.reset();
_pressedButton = -1;
_buttonSelected = false;
}
)
]
)
);
}
Widget _buttonsWidget() {
if (_buttonSelected) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[_buttonWidget(_pressedButton)]
);
} else {
return Row(
children: <Widget>[
_animatedButtonWidget(1),
_animatedButtonWidget(2),
_animatedButtonWidget(3),
_animatedButtonWidget(4)
]
);
}
}
Widget _animatedButtonWidget(int button) {
if (button == _pressedButton) {
return _buttonWidget(button);
} else {
return SizeTransition(
sizeFactor: _animationSlideLeft,
axis: Axis.horizontal,
child: Opacity(
opacity: _animationFadeOut.value,
child: _buttonWidget(button)
)
);
}
}
Widget _buttonWidget(int button) {
return RaisedButton(
onPressed: () => _onButtonClick(button),
child: Text("Button $button")
);
}
void _onButtonClick(int button) {
setState(() {
_pressedButton = button;
});
_controllerFadeOut.forward();
}
}