How to add widget on tree using slide animation on onpressed flutter - flutter

I need to add widgets using slide animation into tree on an action done on onpressed
the parent widget will be a listview in which the childrens will be added in slide down animation

import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
#override
State<StatefulWidget> createState() => HomeState();
}
class HomeState extends State<Home> with SingleTickerProviderStateMixin {
AnimationController controller;
Animation<Offset> offset;
#override
void initState() {
super.initState();
controller =
AnimationController(vsync: this, duration: Duration(seconds: 1));
offset = Tween<Offset>(begin: Offset.zero, end: Offset(0.0, 1.0))
.animate(controller);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
Center(
child: RaisedButton(
child: Text('Show / Hide'),
onPressed: () {
switch (controller.status) {
case AnimationStatus.completed:
controller.reverse();
break;
case AnimationStatus.dismissed:
controller.forward();
break;
default:
}
},
),
),
Align(
alignment: Alignment.bottomCenter,
child: SlideTransition(
position: offset,
child: Padding(
padding: EdgeInsets.all(50.0),
child: CircularProgressIndicator(),
),
),
)
],
),
);
}
}

Related

How to use SlideTransition properly?

I am trying to animate an image (or logo) from center of the screen to top of the screen.
For that, I had a look at SlideTransition. It works, but for portrait orientation only. When the device is rotated, the image goes outside of the screen. (maybe I did not understand the Offset property correctly!) If I make it work for horizontal, the output of the portrait gets changed.
Let me show you the output:
Portrait:
Before animation
After animation (As wanted)
Horizontal:
Before animation
After animation (Problem HERE)
So, as you can see, in horizontal mode, the Logo goes out of the screen.
Here is the code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SlideTransition Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _offsetAnimation;
#override
void initState() {
_controller = AnimationController(vsync: this, duration: Duration(milliseconds: 300));
_offsetAnimation = _controller
.drive(CurveTween(curve: Curves.easeInOut))
.drive(Tween<Offset>(begin: Offset(0.0, 0.0), end: Offset(0.0, -1.0)));
super.initState();
}
#override
void dispose() {
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SlideTransition Demo'),
),
body: Stack(
children: <Widget>[
Align(
alignment: Alignment.center,
child: SlideTransition(
position: _offsetAnimation,
child: FlutterLogo(
size: 200,
),
),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else if (_controller.status == AnimationStatus.dismissed) {
_controller.forward();
}
},
child: Icon(Icons.play_arrow),
),
);
}
}
The SlideTransition does the fractional translation. And this translation depends on the size of the child of the SlideTransition widget.
Tween(begin: Offset(0.0, 0.0), end: Offset(0.0, -1.0))
This tween translates(moves) the FlutterLogo in y-axis for 200(height of the FlutterLogo widget)
Tween(begin: Offset(0.0, 0.0), end: Offset(0.0, -0.5))
This tween translates(moves) the FlutterLogo in y-axis for 100(half-height of the FlutterLogo widget)
If you just want to animate the image from center to topCenter, then you can use AlignTransition.
Example:
class FirstPage extends StatefulWidget {
#override
FirstPageState createState() => FirstPageState();
}
class FirstPageState extends State<FirstPage> with TickerProviderStateMixin {
AnimationController _animationController;
AlignmentGeometryTween _tween;
#override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 3),
);
_tween = AlignmentGeometryTween(
begin: Alignment.center,
end: Alignment.topCenter,
);
}
TickerFuture _play() {
_animationController.reset();
return _animationController.animateTo(
1.0,
curve: Curves.easeInOut,
);
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(title: Text("Demo")),
body: AlignTransition(
alignment: _tween.animate(_animationController),
child: FlutterLogo(
size: 200,
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.play_arrow),
onPressed: _play,
),
),
);
}
}
Demo: DartPad
You can copy paste run full code below
You can use OrientationBuilder and modify Offset to what you need
code snippet
body: OrientationBuilder(
builder: (context, orientation) {
if (orientation == Orientation.portrait) {
_offsetAnimation = _controller
.drive(CurveTween(curve: Curves.easeInOut))
.drive(Tween<Offset>(
begin: Offset(0.0, 0.0), end: Offset(0.0, -1.0)));
} else {
_offsetAnimation = _controller
.drive(CurveTween(curve: Curves.easeInOut))
.drive(Tween<Offset>(
begin: Offset(0.0, 0.0), end: Offset(-1.0, 0.0)));
}
working demo
full code
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SlideTransition Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
Animation<Offset> _offsetAnimation;
Animation<Offset> _offsetAnimationLandscape;
#override
void initState() {
_controller =
AnimationController(vsync: this, duration: Duration(milliseconds: 300));
super.initState();
}
#override
void dispose() {
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SlideTransition Demo'),
),
body: OrientationBuilder(
builder: (context, orientation) {
if (orientation == Orientation.portrait) {
_offsetAnimation = _controller
.drive(CurveTween(curve: Curves.easeInOut))
.drive(Tween<Offset>(
begin: Offset(0.0, 0.0), end: Offset(0.0, -1.0)));
} else {
_offsetAnimation = _controller
.drive(CurveTween(curve: Curves.easeInOut))
.drive(Tween<Offset>(
begin: Offset(0.0, 0.0), end: Offset(-1.0, 0.0)));
}
return Stack(
children: <Widget>[
Align(
alignment: Alignment.center,
child: SlideTransition(
position: _offsetAnimation,
child: FlutterLogo(
size: 200,
),
),
),
],
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if (_controller.status == AnimationStatus.completed) {
_controller.reverse();
} else if (_controller.status == AnimationStatus.dismissed) {
_controller.forward();
}
},
child: Icon(Icons.play_arrow),
),
);
}
}

How to SetState() of Parent when child is Updated in flutter

I have a list view and inside the list view, there is a child widget which can grow when user tap on that.
I want to scroll to the bottom of the list when the user taps on the child and it grows.
when I pass callback function from the parent to the child to scroll to the bottom.
and call the function when the user tap on the child.
I get the following error: setState() or markNeedsBuild() called during build.
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
ScrollController _controller = ScrollController();
void scrollToLast() {
print("trying to scroll");
setState(() {
_controller.animateTo(
_controller.position.maxScrollExtent,
duration: Duration(microseconds: 300),
curve: Curves.easeInOut,
);
});
}
#override
Widget build(BuildContext context) {
return ListView(
controller: _controller,
children: <Widget>[
MyChildWidget(
scrollToLast: this.scrollToLast,
)
],
);
}
}
class MyChildWidget extends StatefulWidget {
final VoidCallback scrollToLast;
MyChildWidget({
this.scrollToLast,
});
#override
_MyChildWidgetState createState() => _MyChildWidgetState(
scrollToLast: this.scrollToLast,
);
}
class _MyChildWidgetState extends State<MyChildWidget> {
final VoidCallback scrollToLast;
_MyChildWidgetState({
this.scrollToLast,
});
int count = 5;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
this.count += 5;
});
this.scrollToLast();
},
child: Column(
mainAxisSize: MainAxisSize.max,
children: List<Widget>.generate(
this.count,
(int index) => Container(
color: Colors.blue,
height: 30,
margin: EdgeInsets.all(10),
),
),
),
);
}
}
You can copy paste run full code below
You can use WidgetsBinding.instance.addPostFrameCallback
code snippet
void scrollToLast() {
print("trying to scroll");
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_controller.animateTo(
_controller.position.maxScrollExtent,
duration: Duration(microseconds: 300),
curve: Curves.easeInOut,
);
});
});
}
working demo
full code
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
ScrollController _controller = ScrollController();
void scrollToLast() {
print("trying to scroll");
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
_controller.animateTo(
_controller.position.maxScrollExtent,
duration: Duration(microseconds: 300),
curve: Curves.easeInOut,
);
});
});
}
#override
Widget build(BuildContext context) {
return ListView(
controller: _controller,
children: <Widget>[
MyChildWidget(
scrollToLast: this.scrollToLast,
)
],
);
}
}
class MyChildWidget extends StatefulWidget {
final VoidCallback scrollToLast;
MyChildWidget({
this.scrollToLast,
});
#override
_MyChildWidgetState createState() => _MyChildWidgetState(
/*scrollToLast: this.scrollToLast,*/
);
}
class _MyChildWidgetState extends State<MyChildWidget> {
/* final VoidCallback scrollToLast;
_MyChildWidgetState({
this.scrollToLast,
});*/
int count = 5;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
setState(() {
this.count += 5;
});
widget.scrollToLast();
},
child: Column(
mainAxisSize: MainAxisSize.max,
children: List<Widget>.generate(
this.count,
(int index) => Container(
color: Colors.blue,
height: 30,
margin: EdgeInsets.all(10),
child: Text('$index'),
),
),
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(
title: "Demo",
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(child: MyWidget()),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Flutter collapse and expand widget with elastic animation

in this simple below ui i have Container right of screen and i want to collapse and expand it with elastic animation, for example on expand elasticIn animation and for collapse elasticOut.
Is this what you need?
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
import 'dart:math';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Spring Box",
theme: ThemeData(),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
Animation animationIn, animationOut;
AnimationController _animationController;
#override
void initState() {
_animationController = AnimationController(
vsync: this,
value: 1.0,
duration: Duration(milliseconds: 500),
);
animationIn = CurvedAnimation(parent: _animationController, curve: Curves.elasticIn);
animationOut = CurvedAnimation(parent: _animationController, curve: Curves.elasticIn);
}
_toggleExpanded() {
if (_animationController.status == AnimationStatus.completed) {
_animationController.reverse();
} else {
_animationController.forward();
}
}
#override
Widget build(BuildContext context) {
var isExpanded = _animationController.status != AnimationStatus.completed;
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _toggleExpanded,
child: Icon(Icons.add),
),
body: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
CollapsAnimation(
animation: isExpanded ? animationOut : animationIn,
child: Container(
color: Color(0xFF404bc4),
),
),
],
),
backgroundColor: Color(0xFFe8e8e8),
);
}
}
class CollapsAnimation extends AnimatedWidget {
CollapsAnimation({key, animation, this.child})
: super(
key: key,
listenable: animation,
);
final Widget child;
final Tween tween = Tween<double>(begin: 0, end: 80);
#override
Widget build(BuildContext context) {
Animation<double> animation = listenable;
var animationValue = tween.evaluate(animation);
double width = animationValue >= 0.0 ? animationValue : 0.0;
return Container(
width: width,
child: child,
);
}
}

flutter moving container from outside of screen to on screen with container size

in this sample code i want to make container with for example 100.0 size of height and move that into screen by animation to show that, like with simple bottom sheet, this sample code work but its not what i want to have
problem is moving that into screen with size of container witch that
is 100.0 on our sample
start animation from bottom height of container into screen
for example:
in this sample code animate move container to top of screen not size of that
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Page(),
);
}
}
class Page extends StatefulWidget {
#override
State<StatefulWidget> createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
Tween<Offset> tween = Tween<Offset>(
begin: Offset(0.0, 10000.0),
end: Offset(0.0, 0.0),
);
Animation<Offset> animation;
AnimationController animationController;
GlobalKey _widgetKey = GlobalKey();
#override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
animation = tween.animate(animationController);
Future<void>.delayed(Duration(seconds: 1), () {
final Size screenSize = MediaQuery.of(context).size;
final RenderBox widgetRenderBox =
_widgetKey.currentContext.findRenderObject();
final Size widgetSize = widgetRenderBox.size;
final double offset = (screenSize.height / 2 / widgetSize.height).ceilToDouble();
tween = Tween<Offset>(
begin: Offset(0.0, offset),
end: Offset(0.0, 0.0),
);
animation = tween.animate(animationController);
this.setState((){
animationController.forward();
});
});
}
#override
void dispose() {
animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('ddddddd'),),
body: Stack(
alignment: Alignment.center,
fit: StackFit.loose,
children: <Widget>[
SlideTransition(
position: animation,
child: Container(
key: _widgetKey,
height:100.0,
width: 300.0,
color:Colors.indigo,
child:Center(
child:Text('ddddddddddddd'),
)
)
),
],
),
);
}
}
I hope I did not misunderstood your question.
The Stack should have the size of the whole screen. Wrap it with SizedBox for example.
No need to center the text. You should rather align it inside the Container itself.
The container should have the width of the screen.
The height of the container is not necessary and therefore we don't need to figure it out.
This works for me:
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: Page(),
);
}
}
class Page extends StatefulWidget {
const Page({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
late final AnimationController _animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
late final Animation<Offset> _animation =
Tween<Offset>(begin: const Offset(0, 1), end: const Offset(0, 0))
.animate(_animationController);
#override
void initState() {
super.initState();
delayedStart();
}
void delayedStart() async {
await Future.delayed(const Duration(seconds: 2), () {
_animationController.forward();
});
}
#override
void dispose() {
_animationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('ddddddd'),
),
body: SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
alignment: Alignment.bottomCenter,
fit: StackFit.loose,
children: <Widget>[
SlideTransition(
position: _animation,
child: Container(
alignment: Alignment.center,
height: 100,
width: MediaQuery.of(context).size.width,
color: Colors.indigo,
child: const Text('ddddddddddddd')
)
),
],
),
),
);
}
}

how to animate collapse elements in flutter

How can i expand and collapse widget when user taps on different widget ( sibling or parent ) with animation ?
new Column(
children: <Widget>[
new header.IngridientHeader(
new Icon(
Icons.fiber_manual_record,
color: AppColors.primaryColor
),
'Voice Track 1'
),
new Grid()
],
)
I want user to be able to tap on header.IngridientHeader and then Grid widget should toggle ( hide if visible and other way around )
edit:
im trying to do something that in bootstrap is called Collapse. getbootstrap.com/docs/4.0/components/collapse
edit 2:
header.IngridientHeader should stay in place all the time
Grid() is scrollable ( horizontal ) widget.
If you want to collapse a widget to zero height or zero width that has a child that overflow when collapsed, I would recommend SizeTransition or ScaleTransition.
Here is an example of the ScaleTransition widget being used to collapse the container for the four black buttons and status text. My ExpandedSection widget is used with a column to get the following structure.
An example of a Widget that use animation with the SizeTransition widget:
class ExpandedSection extends StatefulWidget {
final Widget child;
final bool expand;
ExpandedSection({this.expand = false, this.child});
#override
_ExpandedSectionState createState() => _ExpandedSectionState();
}
class _ExpandedSectionState extends State<ExpandedSection> with SingleTickerProviderStateMixin {
AnimationController expandController;
Animation<double> animation;
#override
void initState() {
super.initState();
prepareAnimations();
_runExpandCheck();
}
///Setting up the animation
void prepareAnimations() {
expandController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500)
);
animation = CurvedAnimation(
parent: expandController,
curve: Curves.fastOutSlowIn,
);
}
void _runExpandCheck() {
if(widget.expand) {
expandController.forward();
}
else {
expandController.reverse();
}
}
#override
void didUpdateWidget(ExpandedSection oldWidget) {
super.didUpdateWidget(oldWidget);
_runExpandCheck();
}
#override
void dispose() {
expandController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizeTransition(
axisAlignment: 1.0,
sizeFactor: animation,
child: widget.child
);
}
}
AnimatedContainer also works but Flutter can complain about overflow if the child is not resizable to zero width or zero height.
Alternatively you can just use an AnimatedContainer to mimic this behavior.
class AnimateContentExample extends StatefulWidget {
#override
_AnimateContentExampleState createState() => new _AnimateContentExampleState();
}
class _AnimateContentExampleState extends State<AnimateContentExample> {
double _animatedHeight = 100.0;
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(title: new Text("Animate Content"),),
body: new Column(
children: <Widget>[
new Card(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new GestureDetector(
onTap: ()=>setState((){
_animatedHeight!=0.0?_animatedHeight=0.0:_animatedHeight=100.0;}),
child: new Container(
child: new Text("CLICK ME"),
color: Colors.blueAccent,
height: 25.0,
width: 100.0,
),),
new AnimatedContainer(duration: const Duration(milliseconds: 120),
child: new Text("Toggle Me"),
height: _animatedHeight,
color: Colors.tealAccent,
width: 100.0,
)
],
) ,
)
],
),
);
}
}
I think you are looking for ExpansionTile widget. This takes a title property which is equivalent to header and children property to which you can pass widgets to be shown or hidden on toggle.
You can find an example of how to use it here.
Simple Example Usage:
new ExpansionTile(title: new Text("Numbers"),
children: <Widget>[
new Text("Number: 1"),
new Text("Number: 2"),
new Text("Number: 3"),
new Text("Number: 4"),
new Text("Number: 5")
],
),
Hope that helps!
Output:
Code:
class FooPageState extends State<SoPage> {
static const _duration = Duration(seconds: 1);
int _flex1 = 1, _flex2 = 2, _flex3 = 1;
#override
Widget build(BuildContext context) {
final total = _flex1 + _flex2 + _flex3;
final height = MediaQuery.of(context).size.height;
final height1 = (height * _flex1) / total;
final height2 = (height * _flex2) / total;
final height3 = (height * _flex3) / total;
return Scaffold(
body: Column(
children: [
AnimatedContainer(
height: height1,
duration: _duration,
color: Colors.red,
),
AnimatedContainer(
height: height2,
duration: _duration,
color: Colors.green,
),
AnimatedContainer(
height: height3,
duration: _duration,
color: Colors.blue,
),
],
),
);
}
}
Thanks to #Adam Jonsson, his answer resolved my problem. And this is the demo about how to use ExpandedSection, hope to help you.
class ExpandedSection extends StatefulWidget {
final Widget child;
final bool expand;
ExpandedSection({this.expand = false, this.child});
#override
_ExpandedSectionState createState() => _ExpandedSectionState();
}
class _ExpandedSectionState extends State<ExpandedSection>
with SingleTickerProviderStateMixin {
AnimationController expandController;
Animation<double> animation;
#override
void initState() {
super.initState();
prepareAnimations();
_runExpandCheck();
}
///Setting up the animation
void prepareAnimations() {
expandController =
AnimationController(vsync: this, duration: Duration(milliseconds: 500));
animation = CurvedAnimation(
parent: expandController,
curve: Curves.fastOutSlowIn,
);
}
void _runExpandCheck() {
if (widget.expand) {
expandController.forward();
} else {
expandController.reverse();
}
}
#override
void didUpdateWidget(ExpandedSection oldWidget) {
super.didUpdateWidget(oldWidget);
_runExpandCheck();
}
#override
void dispose() {
expandController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return SizeTransition(
axisAlignment: 1.0, sizeFactor: animation, child: widget.child);
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
appBar: AppBar(
title: Text('Demo'),
),
body: Home(),
),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool _expand = false;
#override
Widget build(BuildContext context) {
return Column(
children: [
Header(
onTap: () {
setState(() {
_expand = !_expand;
});
},
),
ExpandedSection(child: Content(), expand: _expand,)
],
);
}
}
class Header extends StatelessWidget {
final VoidCallback onTap;
Header({#required this.onTap});
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
color: Colors.cyan,
height: 100,
width: double.infinity,
child: Center(
child: Text(
'Header -- Tap me to expand!',
style: TextStyle(color: Colors.white, fontSize: 20),
),
),
),
);
}
}
class Content extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.lightGreen,
height: 400,
);
}
}
Another solution that doesn't require an animation controller is using AnimatedSwitcher widget with SizeTransition as a transition builder.
here is a simple example:
AnimatedSwitcher(
duration: Duration(milliseconds: 300),
transitionBuilder: (child, animation) {
return SizeTransition(sizeFactor: animation, child: child);
},
child: expanded ? YourWidget() : null,
)
Of course you can customize the curve and layout builder for the animation.