Using a ScrollController and TextFormField FocusNode to scrollController.animateTo scrollController.position.maxSCrollExtent I cannot achieve displaying my "submit" button at the bottom of the view when the keyboard displays to for entry into the TextFormField above my "submit" button.
Note I have tried with resizeToAvoidBottomInset set to true and false.
My SingleChildScrollView code snippet
ScrollConfiguration(
behavior: ScrollBehaviorHideSplash(),
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[...widgets
My my FocusNode on focus function:
void _scrollToBottom() {
print('scrollToBottom');
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 750),
);
}
I have also tried _scrollController.position.maxScrollExtent + 400.0 just in case.
Content display with having resizeToAvoidBottomInset: true:
Where I would like the ScrollController to scroll to but have to currently manually scroll to see the button:
Try this:
Timer(Duration(milliseconds: 100), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 750),
);
});
A little bit hacky but adding Timer before scroll animation solved my problem, i guess listview needs some time to add new item.
In my case I had a SingleChildScrollView and a ListView.separated inside of it, I passed the scroll controller to the inner listview instead of the parent scroll view (the max scroll ), the height of the view was changing based on the appearance of the keyboard so waiting time was required.
Future.delayed(
Duration(milliseconds: 200),
() {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 400),
curve: Curves.ease,
);
},
);
Related
I am trying to design a music playlist page. So far I am able to create a listview with song cards. When I click on any song, a custom bottom bar appears and the audio starts playing. However, I just hold a state with boolean and show the bottom bar according to that. Instead, I want it to appear like sliding up and reach to the position. Let say in 0.5 seconds.
I have a custom NavBar class
class NavBar extends StatefulWidget
And I use this class in build similar to:
return Column(
children: [
SizedBox(
height: constraints.maxHeight * 0.5,
hild: SlidingBanners(),
),
Expanded(
child: Lists(),
),
NavBar()
],
);
How can I such animation?
Use a SizeTransition widget https://api.flutter.dev/flutter/widgets/SizeTransition-class.html
"SizeTransition acts as a ClipRect that animates either its width or
its height, depending upon the value of axis. The alignment of the
child along the axis is specified by the axisAlignment."
Widget build(BuildContext context) {
return SizeTransition(
sizeFactor: CurvedAnimation(
curve: Curves.fastOutSlowIn,
parent: _animationController,
),
child: Container(height: 100, color: Colors.blue)
);
}
init animation controller in stateful widgets initState()
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this, duration: const Duration(milliseconds: 500));
}
Make sure your stateful widget uses SingleTickerProviderStateMixin
class _NavBarState extends State<NavBar>
with SingleTickerProviderStateMixin {
late AnimationController _animationController;
then open and close with
_animationController.forward()
_animationController.reverse()
You can pass the _animationController into the NavBar widget's constructor from its parent if you want the parent to control the animation.
Alternatively you can use an AnimatedContainer widget and setState with its height 0 or 100 depending on if Nav should be shown. This becomes a problem for some widgets that cannot be squished to height of 0 though and I would not recommend for any container that contains anything but text
One solution would be to use a SnackBar widget. Since it's automatically animated, you wouldn't want to worry about manually animating the bottom bar. You can insert your Audio Player (bottom bar) widget to the child of the SizedBox.
The bottom bar is made visible by,
ScaffoldMessenger.of(context).showSnackBar(snackBar);
This bottom bar is dismissed (hidden) by dragging down or by,
ScaffoldMessenger.of(context).hideCurrentSnackBar();
There maybe many other solutions as well to this, but reading your question, I hope this is what you wanted.
return Center(
child: ElevatedButton(
onPressed: () {
final snackBar = SnackBar(
duration: Duration(days: 365),
content: SizedBox(
height: 100,
//insert your audio player widget here
child: Column(
children: [
Text("YOUR AUDIOPLAYER WIDGET HERE"),
Text("Audio Controls Here"),
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
},
child: Text("Audio Player Minimize"),
),
],
),
),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
child: Text('Open Audio Player'),
),
);
Basically, I have a Scrollable Column of Sections. Each section is an Expansion Tile, with a [ ListView ] as children attribute. (ListView uses NeverScrollPhysics, so all the children of the ListView are built at once). When I open a section (expand the tile), I want the Scrollview to scroll, such as the opened tile (only one tile can be opened at time) is located at the very top. As you can see, if there is no enough space in the bottom to make the column scrollable, I add an extra one. I call animateTo(), inside addPostFrameCallback. Before I migrated to Flutter 2, it worked perectly both in release and debug mode. But now it works fine only in debug mode, In release, the scrollview does not scroll (I've checked that scrollOffset is calulated correctly). In release mode it works only when I fastly change the collapse state from open-close-open. And also it appears to work fine for the sections that are in the very bottom. For which the extra space should be added. In other cases it just expands and no scroll is performed.
return LayoutBuilder(builder: (context, constraints) {
var selectedSection = sections.firstWhere(
(element) => element.isExpanded,
orElse: () => null);
var selectedSectionIndex = sections.indexOf(selectedSection);
scrollOffset = calculateScrollOffset(selectedSectionIndex);
double bottomExtraSpaceHeight = calculateBottomExtraSpaceHeight(
constraints.maxHeight,
scrollOffset,
selectedSection,
sections,
);
bottomExtraSpace = SizedBox(height: bottomExtraSpaceHeight);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (_scrollController.hasClients) {
_scrollController.animateTo(scrollOffset,
duration: Duration(milliseconds: 350),
curve: Curves.easeIn);
}
});
return Container(
alignment: Alignment.topCenter,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFiltersRow(),
buildSections(sections),
bottomExtraSpace
],
),
),
);
})
If I change
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if (_scrollController.hasClients) {
_scrollController.animateTo(scrollOffset,
duration: Duration(milliseconds: 350), curve: Curves.easeIn);
}
});
to
Future.delayed(Duration(milliseconds: 200)).then((_) {
if (_scrollController.hasClients) {
_scrollController.animateTo(scrollOffset,
duration: Duration(milliseconds: 350),
curve: Curves.easeIn);
}
});
it start to work fine, but it does not seem like a good solution. Any thoughts about what I am doing wrong and how it can be fixed in a proper way would be appreciated. Thanks!
I am building a WebApp in flutter and I have a SingleChildScrollView with some widgets inside. I want the buttons on the appbar to take me to the correspondent widget when I press on the buttons.
Is that possible? Here I attach the part of the code.
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: CustomAppBar(),
backgroundColor: Colors.white,
body: SingleChildScrollView(
controller: widget.homeController,
child: Column(
children: [
Inicio(),
Services(),
QuienesSomos(),
ContactForm(),
BottomInfo(),
],
),
),
);
}
So I have one button on the appbar per each children in the SingleChildScrollView and I would like that when I press the correspondent button, it scrolls down to the correspondent section on the widget. I tried with Navigator.of().PushNamed but it opens a new screen instead of scrolling down. Any ideas? Thanks in advance!
To control the position, you have to manage the controller of the SingleChildScrollView .
If you want to smoothly go a section, you can attach functionality to control the SingleChildScrollView controller to the button:
widget.homeController.animateTo(
0.0, // change 0.0 {double offset} to corresponding widget position
duration: Duration(seconds: 1),
curve: Curves.easeOut,
);
If you just want to instantly jump to the position:
widget.homeController.jumpTo(0.0); // change 0.0 {double value} to corresponding widget position
Make a scroll controller:
ScrollController myController = ScrollController();
and attach it to your SingleChildScrollView widget:
SingleChildScrollView(
controller: myController,
child: ...
Now create a GlobalKey:
final anchor = GlobalKey();
Attach it to any of your widget:
Container(
key: anchor,
child: ...
),
That's it, now you can programmatically scroll to this widget using scroll controller:
myController.position.ensureVisible(
anchor.currentContext.findRenderObject(),
alignment: 0.5,
duration: const Duration(seconds: 1),
);
I could achieve my goal by using,
onPressed: () {
Scrollable.ensureVisible(servicesKey.currentContext,
duration: Duration(seconds: 1),
curve: Curves.easeOut);
},
and by asigning the corresponding key in each widget.
the effect I want to achieve is that the user scrolls sideways when the user uses the gesture to scroll down. Something like on this page.
ListView does not work with gestures - but it does work with a mouse, but that doesn't solve the problem when the user is using gestures, e.g. on a laptop.
Does anyone have an idea how to handle it?
I did not find any dedicated functionality in some widget, but here is my workaround:
final scrollController = ScrollController();
Listener(
onPointerSignal: (pointerSignal) {
if (pointerSignal is PointerScrollEvent) {
scrollController.animateTo(
scrollController.offset + pointerSignal.scrollDelta.dy,
curve: Curves.decelerate,
duration: const Duration(milliseconds: 200),
);
}
},
child: SingleChildScrollView( // or any other
controller: scrollController,
child: ...,
),
)
This problem is pretty much very similar to messenger's icons. As long as you press that icon, the larger it gets.
This would be fun for others to use soon!
I'm new to flutter but if someone has an idea on how to implement this, I'm very much willing to help.
You can use a ScaleTransition widget that accept animation value
_controller = AnimationController(duration: const Duration(milliseconds: 2000), vsync: this, value: 0.1);
_animation = CurvedAnimation(parent: _controller, curve: Curves.bounceInOut);
ScaleTransition(
scale: _animation,
alignment: Alignment.center,
child: child
)
and in your longPress just call
_controller.forward();