Animating expanding / contracting on widgets of unknown size - flutter

We want of to display a list of images and text that we fetch from a database. When you tap on one of the images / texts, they should expand with an animation. With the current code, the expanding animation works as intended, but the opposite does not work. The image shrinks without an animation, and then the AnimatedSize animates. See: Flutter AnimatedSize works in one direction only
Since we don't have a solid color the solution to that question does not really work for us.
It does work with the images with the following code, but it's not ideal and text does not work at all that way.
Container(
foregroundDecoration: BoxDecoration(
image: DecorationImage(
image: CachedNetworkImageProvider(
snapshot.data.documents[index]['image'],
),
fit: BoxFit.cover,
alignment: Alignment.topCenter,
),
),
child: AnimatedSize(
duration: Duration(milliseconds: 300),
vsync: this,
child: Opacity(
opacity: 0,
child: Container(
height: _expandedIndex == index ? null : 250,
child: CachedNetworkImage(
imageUrl: snapshot.data.documents[index]['image'] ?? '',
fit: BoxFit.cover,
width: double.infinity,
),
),
),
),
),
I have found a lot of other people with the same problem, but I have never found a solution that works, so any help is very welcome.
Thanks!

use a sizeTransition Widget.
and set up your animationController and Tween<double>
and it should work fine, I've used it and it worked in both directions forward and reverse.

Tried sizeTransition widget, and it worked for me. AnimationController and Tween are not necessary. Here is my worked code.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
late final Animation<double> _animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextButton(
child: const Text('toggle'),
onPressed: () => _controller.isDismissed
? _controller.forward()
: _controller.reverse(),
),
SizeTransition(
sizeFactor: _animation,
axis: Axis.vertical,
axisAlignment: -1,
child: Container(
color: Colors.amberAccent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: const [Text("Hello"), Text("World")],
),
),
),
],
),
);
}
}

Related

How to center the anchor point of a shape in Flutter?

I have a continer whose length and width are 0, but with the animation, I change its length and width to 200. The problem is that when it starts to grow, it grows from the bottom and does not grow from the middle. I want to know how to place its anchor point in the middle. Working with after effects, they will understand by seeing this image
For example, in the code below, the circle starts growing from the bottom, while I want it to start growing from the middle point of the circle:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
late Animation<double> width;
late AnimationController controller;
#override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(seconds: 2), vsync: this);
width = Tween<double>(begin: 0, end: 200).animate(controller);
width.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(50),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () {
controller.forward();
},
child: Text('a')),
Container(
width: width.value,
height: width.value,
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.black),
),
],
),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
late Animation<double> width;
late AnimationController controller;
Tween<double> animation = Tween<double>(begin: 0, end: 200);
#override
void initState() {
super.initState();
controller =
AnimationController(duration: Duration(seconds: 2), vsync: this);
width = animation.animate(controller);
width.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
alignment: Alignment.center,
padding: EdgeInsets.all(50),
child: Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ElevatedButton(
onPressed: () {
controller.forward();
},
child: Text('a'),
),
SizedBox(height: animation.end!-width.value/2),
Container(
width: width.value,
height: width.value,
decoration:
BoxDecoration(shape: BoxShape.circle, color: Colors.black),
),
],
),
),
);
}
}
I believe you have a positioning issue. I moved the circle a little bit and now it seems that it starts to grow from the center. Please let me know if this code fixes your issue:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin {
late Animation<double> width;
late AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(duration: Duration(seconds: 2), vsync: this);
width = Tween<double>(begin: 0, end: 200).animate(controller);
width.addListener(() {
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
controller.forward();
},
child: Text('a')),
AnimatedContainer(
width: width.value,
height: width.value,
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.black),
duration: controller.duration!,
),
],
),
),
);
}
}

How to build Animated splash screen with Getx?

I am confused about this. I Don't know how to update the value to animate the image in the splash screen. With the stateful widget class we call setState(() {}); inside the listener to update the value. But, How do I achieve it with the Getx?
Animation with Stateful Widget :
animationInitilization() {
animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
animation =
CurvedAnimation(parent: animationController, curve: Curves.easeOut);
animation!.addListener(() {
setState(() {});
});
animationController.forward();
}
Animation with Getx:
Splash Screen :
class SplashScreen extends StatelessWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
SplashScreenViewModel splashScreenViewModel =
Get.put(SplashScreenViewModel());
return Scaffold(
body: Stack(
fit: StackFit.expand,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/logo.png',
width: splashScreenViewModel.animation.value * 200,
height: splashScreenViewModel.animation.value * 200,
),
],
),
],
),
);
}
}
Getx Controller :
class SplashScreenViewModel extends GetxController
with GetSingleTickerProviderStateMixin {
late AnimationController animationController;
late Animation<double> animation;
#override
void onInit() {
animationInitilization();
super.onInit();
}
animationInitilization() {
animationController =
AnimationController(vsync: this, duration: const Duration(seconds: 2));
animation =
CurvedAnimation(parent: animationController, curve: Curves.easeOut)
.obs
.value;
animation.addListener(() => update());
animationController.forward();
}
}
Nice animation for SplashScreen bro.
I recommend using GetBuilder() to make it work properly:
class SplashScreen extends StatelessWidget {
const SplashScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: GetBuilder<SplashScreenViewModel>(
init: SplashScreenViewModel(),
builder: (controller) {
return Stack(
fit: StackFit.expand,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/logo.png',
width: controller.animation.value * 200,
height: controller.animation.value * 200,
),
],
),
],
);
},
),
);
}
}

How to animate the swap of 2 items in a Row?

I want to make something very simple. There's a Row with 2 widgets. When I press a button, they swap orders. I want this order swap to be animated.
I've loked at AnimatedPositioned but it requires a Stack. What would be the best way of doing such thing?
I thought Animating position across row cells in Flutter answered this but it's another different problem
You can easily animate widgets in a Row with SlideAnimation. Please see the code below or you may directly run the code on DartPad https://dartpad.dev/e5d9d2c9c6da54b3f76361eac449ce42 Just tap on the colored box to swap their positions with an slide animation.
SlideAnimation
Animates the position of a widget relative to its normal position.
The translation is expressed as an Offset scaled to the child's size.
For example, an Offset with a dx of 0.25 will result in a horizontal
translation of one quarter the width of the child.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
with SingleTickerProviderStateMixin {
AnimationController _controller;
List<Animation<Offset>> _offsetAnimation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
);
_offsetAnimation = List.generate(
2,
(index) => Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: Offset(index == 0 ? 1 : -1, 0.0),
).animate(_controller),
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
void _animate() {
_controller.status == AnimationStatus.completed
? _controller.reverse()
: _controller.forward();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Flutter Demo Row Animation")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
BoxWidget(
callBack: _animate,
text: "1",
color: Colors.red,
position: _offsetAnimation[0],
),
BoxWidget(
callBack: _animate,
text: "2",
color: Colors.blue,
position: _offsetAnimation[1],
)
],
),
RaisedButton(
onPressed: _animate,
child: const Text("Swap"),
)
],
),
),
);
}
}
class BoxWidget extends StatelessWidget {
final Animation<Offset> position;
final Function callBack;
final String text;
final Color color;
const BoxWidget(
{Key key, this.position, this.callBack, this.text, this.color})
: super(key: key);
#override
Widget build(BuildContext context) {
return SlideTransition(
position: position,
child: GestureDetector(
onTap: () => callBack(),
child: Container(
margin: const EdgeInsets.all(10),
height: 50,
width: 50,
color: color,
child: Center(
child: Container(
height: 20,
width: 20,
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: Center(child: Text(text)),
),
),
),
),
);
}
}

Flutter. How can I make container wider than screen?

I'm trying to create a parallax background for page controller. For that purpuse I need to create a background image that is wider than the screen. I've put it inside a container like this:
#override
Widget build(BuildContext context) {
return Material(
child: Stack(
children: [
Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat
)
)
),
],
),
);
}
But the problem is that no matter what width I specify, the container (and the image, of course) never get wider than the screen. Is it possible at all?
p.s. I tried to use SizedBox and AspectRatio widgets, and they both give the same result
try this, as an option
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
],
),
),
);
}
}
also you can disable scroll for user and manage scroll position via scroll controller
SingleChildScrollView(
scrollDirection: Axis.horizontal,
physics: const NeverScrollableScrollPhysics(),
controller: controller, // your ScrollController
child: Container(
width: 4000,
height: 250,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
For images you can use Transform.scale(), as found in the documentation. Using your example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: [
Align(
alignment: Alignment.center,
child: Transform.scale(
scale: 10.0,
child: Container(
width: 400,
height: 25,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/pizza_bg.png'),
fit: BoxFit.cover,
repeat: ImageRepeat.noRepeat,
),
),
),
),
),
],
),
),
);
}
}
If you want to animate the scale, you can use ScaleTransition(), explained in this page of the docs. For example:
/// Flutter code sample for ScaleTransition
// The following code implements the [ScaleTransition] as seen in the video
// above:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
/// AnimationControllers can be created with `vsync: this` because of TickerProviderStateMixin.
class _MyStatefulWidgetState extends State<MyStatefulWidget>
with TickerProviderStateMixin {
AnimationController _controller;
Animation<double> _animation;
#override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true);
_animation = CurvedAnimation(
parent: _controller,
curve: Curves.fastOutSlowIn,
);
}
#override
void dispose() {
super.dispose();
_controller.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ScaleTransition(
scale: _animation,
child: const Padding(
padding: EdgeInsets.all(8.0),
child: FlutterLogo(size: 150.0),
),
),
),
);
}
}
NOTE: To avoid quality loss in the image, use an image of the size after scaling or a vector graphic as a source.

What is the best way to make an orbiting button

I would like to make something like this:
https://youtu.be/W3O0077GMlo
And I would like for the rotating circle (moon in this video) to act as a button.
What is the best way to do this performance wise?
You can use the RotationTransition inside a Stack widget to create the rotating animation. Inside the Stackset the alignment to center, and wrap your rotating widget inside an Align. Set the alignment attribute of the Align widget to Alignment.topCenter or any outer alignment.
Remember to deploy on release to your phone to make sure the animations are running smooth.
Quick standalone code example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(width: 300.0, height: 300.0, child: OrbitingButton()),
),
),
);
}
}
class OrbitingButton extends StatefulWidget {
#override
_OrbitingButtonState createState() => _OrbitingButtonState();
}
class _OrbitingButtonState extends State<OrbitingButton>
with SingleTickerProviderStateMixin {
AnimationController controller;
#override
void initState() {
super.initState();
controller = AnimationController(vsync: this);
controller.repeat(min: 0.0, max: 1.0, period: Duration(seconds: 1));
}
#override
Widget build(BuildContext context) {
return Stack(
alignment: Alignment.center,
children: <Widget>[
RotationTransition(
turns: controller,
child: Align(
alignment: Alignment.topCenter,
child: Container(
color: Colors.green,
height: 30.0,
width: 30.0,
),
),
),
RaisedButton(
child: Text('Button'),
)
],
);
}
}