Reputation: 1
I am trying to animate an image from the center of the screen to a new position. I am trying to calculate the offset at runtime relative to the widget and screen size. The link below describes how to start the offset offscreen but in my case, i want the transition to start from the center of the screen regardless of the device size.
Flutter SlideTransition begin with Offset OFF SCREEN.
EDIT:
[First image is the splash screen] imagelink
[Second image is the next screen where i want the image to move to from center] imagelink2
Code i have tried. Here i am trying to get the height of the screen in half and use that as the start position. But the maths is wrong.
Future<void>.delayed(Duration(seconds: 0), () {
final Size screenSize = MediaQuery.of(context).size;
print('screen size: $screenSize');
double offsetY = screenSize.height / 2 / 1000;
print('offsetY: $offsetY');
tween = Tween<Offset>(
begin: Offset(0.0, offsetY),
end: Offset(0.0, 0.0),
);
_offsetAnimation = tween.animate(
CurvedAnimation(
parent: _controller,
curve: Curves.bounceIn,
),
);
this.setState(() {
// Animate it.
Timer(
Duration(milliseconds: 3000),
() => _controller.forward(),
);
});
});
Upvotes: 0
Views: 6918
Reputation: 5601
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Page()
),
);
}
}
class Page extends StatefulWidget {
@override
State<StatefulWidget> createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
AnimationController animationController;
@override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 2),
);
animationController.repeat(); //just to show it can be animated
}
@override
void dispose() {
// Don't forget to dispose the animation controller on class destruction
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SlideTransition(
position: Tween<Offset>(begin: Offset.zero,
end: Offset(1,0)).animate(animationController),
child: Container(
height: double.infinity,
child: Center(
child: CircleAvatar(
backgroundImage: NetworkImage(
'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
),
radius: 50.0,
),
)),
);
}
}
The trick here is to animate the whole container (the size of the screen) but with a child in a center of another size. In the Tween you define the begining with Offset.zero (the position at the center) and at the end the Offset where you want it to move. If you want a bit more control you can also use a PositionTransition with a Stack, but for that I would need to see a bit of your code or some pictures of what are you aiming for
UPDATE
Ok what you can do to achieve that animation is to use an Animation Controller and having 2 Tweens (one for the alignment of the image and one for opacity of the buttons)
class Page2 extends StatefulWidget {
@override
State<StatefulWidget> createState() => _PageState2();
}
class _PageState2 extends State<Page2> with SingleTickerProviderStateMixin{
AnimationController animationController;
Animation<AlignmentGeometry> _align;
Animation<double> _opacity;
@override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: Duration(seconds: 1),
);
_align = AlignmentGeometryTween(begin: Alignment.bottomCenter, end: Alignment.center).animate(animationController);
_opacity = Tween<double>(begin: 0, end: 1).animate(CurvedAnimation(parent: animationController, curve: Interval(0.5, 1), reverseCurve: Interval(0.5, 1.0)));
//The next piece of code is just for test
animationController.forward();
animationController.addStatusListener((status) {
if(status == AnimationStatus.completed)
Future.delayed(const Duration(milliseconds: 800), () => animationController.reverse());
else if(status == AnimationStatus.dismissed)
Future.delayed(const Duration(milliseconds: 800), () => animationController.forward());
});
}
@override
void dispose() {
animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.teal[200]],
begin: Alignment.bottomLeft,
end: Alignment.topRight,
stops: [0.5, 0.85])
),
child: Column(
children: [
Expanded(
flex: 1,
child: AnimatedBuilder( //create an AnimatedBuilder and provide the Animation of the align
animation: _align,
child: CircleAvatar(
backgroundImage: NetworkImage(
'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
),
radius: 50.0,
),
builder: (context, child){
return Align(
alignment: _align.value, //use the align value
child: child
);
},
)
),
Expanded(
child: FadeTransition( //simply use this widget that receives an Animation<double> to animate opacity from 0 to 1
opacity: _opacity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
width: double.infinity,
child: RaisedButton(
child: Text('Log in', style: TextStyle(color: Colors.teal[300], fontSize: 12)),
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
onPressed: () => print('Log in'),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
width: double.infinity,
child: RaisedButton(
child: Text('Sign up', style: TextStyle(color: Colors.white70, fontSize: 12)),
color: Colors.teal[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
onPressed: () => print('Sign up'),
),
)
]
),
)
),
]
)
);
}
}
If you don't care about the animationController and just want to animate once (maybe when first displaying the page) you can make use of some widgets that extend ImplicitAnimations, this class does all the animations for you without you creating the controllers, just by setState and changing their value
class Page1 extends StatefulWidget {
@override
State<StatefulWidget> createState() => _PageState1();
}
class _PageState1 extends State<Page1>{
double opacity = 0;
double padding = 0;
bool align = false;
@override
void initState() {
super.initState();
//setState after 300ms just to see the changes
Future.delayed(const Duration(milliseconds: 300), () => _setAnimation());
}
_setAnimation() => setState((){
opacity = opacity == 0 ? 1 : 0; //when you setState to different values it will animate to those values
align = !align;
});
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.teal[200]],
begin: Alignment.bottomLeft,
end: Alignment.topRight,
stops: [0.5, 0.85])
),
child: Column(
children: [
Expanded(
flex: 1,
child: AnimatedAlign(
duration: const Duration(milliseconds: 300),
alignment: align ? Alignment.center : Alignment.bottomCenter, //pass an Align based on the bool value, or if you want to directly pass an Alignment
child: CircleAvatar(
backgroundImage: NetworkImage(
'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
),
radius: 50.0,
),
)
),
Expanded(
child: AnimatedOpacity(
opacity: opacity, //give the opacity value
duration: const Duration(milliseconds: 500),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
width: double.infinity,
child: RaisedButton(
child: Text('Log in', style: TextStyle(color: Colors.teal[300], fontSize: 12)),
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
onPressed: () => print('Log in'),
),
),
Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 0),
width: double.infinity,
child: RaisedButton(
child: Text('Sign up', style: TextStyle(color: Colors.white70, fontSize: 12)),
color: Colors.teal[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16.0),
),
onPressed: () => _setAnimation(),
),
)
]
)
),
),
]
)
);
}
}
This kind of widgets also have a VoidCallback called onEnd, so if you want to stager some animation, let's say move the image and until finished change the opacity just change it like this
_setAnimation() => setState((){
//don't change opacity here
align = !align;
});
and then in the onEnd of the AnimatedAlign
AnimatedAlign(
duration: const Duration(milliseconds: 300),
alignment: align ? Alignment.center : Alignment.bottomCenter,
child: CircleAvatar(
backgroundImage: NetworkImage(
'https://pbs.twimg.com/media/DpeOMc3XgAIMyx_.jpg',
),
radius: 50.0,
onEnd: () => setState(() => opacity = opacity == 0 ? 1 : 0) //when the align animation ends, it will change the opacity
),
Upvotes: 1