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.
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'),
),
);
I have a container that starts at zero height and needs to be expanded after a user interaction.
I tried using AnimatedContainer / AnimatedSize and changing the child widget's height from 0 to null, but in both cases, Flutter complains that it cant' interpolate from 0 to null.
I've also tried using BoxConstraints (with expanded using maxHeight = double.infinity) instead of explicit heights, in which case Flutter complains it can't interpolate from a finite value to an indefinite one.
I've also tried setting mainAxisSize to min/max, in which case Flutter complains that vsync is null.
How do I animate expanding a widget such that it dynamically grows big enough to wrap its contents? And if this can't be done dynamically, what's a safe way to size contents such that they make sense across screen sizes? In web dev, I know things like em are sort of relative sizing, but in the context of Flutter, I don't see how to control the size of things reliably.
Update: As suggested by #pskink, wrapping the child in an Align widget and animating Align's heightFactor param accomplishes collapsing. However, I'm still having trouble getting collapse to work when the collapsing child itself has children. For example, Column widgets don't clip at all with ClipRect (see https://github.com/flutter/flutter/issues/29357), and even if I use Wrap instead of Column, that doesn't work if the Wrap's children are Rows. Not sure how to get clipping to work consistently.
Maybe you could also solve this with a SizeTransition?
class VariableSizeContainerExample extends StatefulWidget {
VariableSizeContainerExample();
#override
_VariableSizeContainerExampleState createState() => _VariableSizeContainerExampleState();
}
class _VariableSizeContainerExampleState extends State<VariableSizeContainerExample> with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastLinearToSlowEaseIn,
);
}
_toggleContainer() {
print(_animation.status);
if (_animation.status != AnimationStatus.completed) {
_controller.forward();
} else {
_controller.animateBack(0, duration: Duration(seconds: 1));
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: Column(
children: [
TextButton(
onPressed: () => _toggleContainer(),
child: Text("Toggle container visibility"),
),
SizeTransition(
sizeFactor: _animation,
axis: Axis.vertical,
child: Container(
child: Text(
"This can have variable size",
style: TextStyle(fontSize: 40),
),
),
),
Text("This is below the above container"),
],
),
),
),
);
}
}
Moving #pskink's comments to an answer for posterity:
The main concept is that the Align widget has a property called heightFactor, which takes a double between 0 and 1 to scale its child's height (there's also a similar widthFactor property for width). By animating this property, we can collapse/expand the child. For example:
ClipRect(
child: Align(
alignment: alignment,
child: Align(
alignment: innerAlignment,
widthFactor: constantValue,
heightFactor: animatedValue.value,
child: builder(context, animation),
),
)
)
where animatedValue is of type Animation<double>, and ClipReact is used to clip/truncate the child widget. Note that ClipReact needs to be wrapped outside the Align widget; it doesn't work consistently when wrapping Align's child widget.
Edit: it's also necessary for the recipient of the animation to be an AnimatedWidget for things to go smoothly. See selected answer for an approach that handles this for you.
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: ...,
),
)
I need help to do the following: when I press List 1, the screen focuses on List 1; I need the same for the rest of the options
This is the code for the example:
code
This behavior already exists in web pages but I haven't found this same behavior at the mobile app level. Thank you
Here is a small code snippet of something similar which might help you achieve you desired results.
By clicking the fab icon it will scroll down to item 35 within the ListView.
class MyHomePage extends StatelessWidget {
final _scrollController = ScrollController();
final _cardHeight = 200.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.orange,
onPressed: () => _animateToIndex(35),
child: Icon(Icons.add),
),
body: ListView.builder(
controller: _scrollController,
itemCount: 100,
itemBuilder: (_, i) => Container(
height: _cardHeight,
child: Card(
color: Colors.lightBlue,
child: Center(
child: Text("Scroll Item $i", style: TextStyle(fontSize: 28.0),),
),
),
),
),
);
}
_animateToIndex(index) {
_scrollController.animateTo(_cardHeight * index,
duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn);
}
}
You'll need to have a scrollable Widget (like ListView, SingleScrollableWidget) instead of a Column in ListSecondPage.
Then add a ScrollController to it and ListSecondPage should receive which button was tapped. Based on that selection you can scroll to the desired location with the ScrollController
Does anyone know if there is a way to remove the PageView indicator which is shown for a couple of seconds after I change the Page?
return Scaffold(
bottomNavigationBar: CubertoBottomBar(
tabStyle: CubertoTabStyle.STYLE_FADED_BACKGROUND,
selectedTab: _selectedIndex,
tabs: [
TabData(
iconData: Icons.assignment,
title: "Logbuch",
tabColor: Colors.deepPurple,
),
....
],
onTabChangedListener: (position, title, color) {
setState(() {
_selectedIndex = position;
});
},
),
body: PageView(
controller: _pageController,
pageSnapping: true,
onPageChanged: (index) {
setState(() {
_selectedIndex = index;
});
},
// physics: NeverScrollableScrollPhysics(),
children: <Widget>[
LogBookView(),
...
],
),
);
I couldn't find anything about it in the documentation of the PageView widget and the BottomNav widget.
I think you need to provide a little context to your code since there are loads of ways to implement PageView and indicator. I think you are using TabBarView as parent to the PageView because By default PageView doesn't have indicator itself, TabBarView has.
assuming I am right you really don't need to use TabBarView unless you want to use different tabs, and each tab has different PageView or other widgets. Judging your UI i think PageView with controller can satisfy your UI need.
Class XYZ extends StatefullWidget{
.......
}
Class _XYZ extends State<XYZ>
with SingleTickerProviderStateMixin{
PageController _pageController;
int pageNumber = 0;
void initState() {
super.initState();
_pageController = PageController();
}
..................
//inside build PageView
PageView(
controller: _pageController,
onPageChanged: (pageIndex) {
setState(() {
pageNumber = pageIndex;
});
},
children: <Widget>[
Notes(), // scaffold or widget or other class
Wochenplan(), scaffold or widget or other class
ETC(), // scaffold or widget or other class
],
),
..................
//inside build bottom nav icons
Row(
children: <Widget>[
buildTabButton(int currentIndex, int pageNumber, String image),
buildTabButton2....,
buildTabButton3...,
],
)
.......
buildTabButton1........
buildTabButton2(int currentIndex, int pageNumber, String image) {
return AnimatedContainer(
height: _height,
width: currentIndex == pageNumber ? 100 : 30,
padding: EdgeInsets.all(10),
duration: Duration(milliseconds: 300),
decoration: BoxDecoration(),// use currentIndex == pageNumber to decorate current widget and others
child: ../*ClickableWidget*/, //onClckWochenplan() // and so on
);
}
buildTabButton3........
........................
//onPressed or onTap functions which you can implement inside widgets as well..
void ABC() {
_pageController.animateToPage(0,
duration: Duration(milliseconds: 500), curve: Curves.decelerate);
}
void onClckWochenplan() {
_pageController.animateToPage(1,
duration: Duration(milliseconds: 500), curve: Curves.decelerate);
}
void DEF() {
_pageController.animateToPage(2,
duration: Duration(milliseconds: 500), curve: Curves.decelerate);
}
I hope It helps.. else provide some code to get help from the community.. cheers
I see you are using library CubertoBottomBar which is not default TabBarView that flutter provides. So I looked into the library. The thing is, it should not suppose to show the indicator at all. So i am going to take 2 wild guesses here and 1 suggestion..
1. You might have another parents with TabBarView wrapping up this scaffold (may be from previous class/widget where this scaffold is being wrapped)..
Scaffold(
bottomNavigationBar: CubertoBottomBar(
tabStyle: CubertoTabStyle.STYLE_FADED_BACKGROUND,
selectedTab: _selectedIndex,
tabs: [
..............
if you are building it on IOS it might not rendering the library widget correctly. (this is since i tried to build this app in the same way you did and i did not get any indicator at all with 3 of my devices). So,if you are currently building it on IOS, Try to build on android device then you might know whats really going on and report an issue to the library owner.
Apart from my inability to help you, i suggest you to try to replace the PageView with TabBar then you can control the indicator (Though the PageView should not give you indicator either but just a suggestion)..
TabBar(
indicatorWeight: 0.0, // then you can control height of indicator
indicatorColor: Colors.transparent, // then you can control color with transparent value
tabs: <Widget>[
tab();
tab2();
],
),
//**TODO ----------------------
My bet is on my 1st Point.. check your widget tree thoroughly...