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,
),
)),
);
}
}
这里的诀窍是为整个容器(屏幕大小)设置动画,但在另一个大小的中心有一个孩子。在 Tween 中,您使用 Offset.zero (在中心的位置)定义开始,在您希望它移动的位置结束时定义 Offset。如果你想要更多的控制,你也可以使用带有堆栈的 PositionTransition,但为此我需要查看你的一些代码或一些关于你的目标的图片
更新
好吧,你可以做些什么来实现该动画是使用一个动画控制器并有 2 个补间(一个用于对齐图像,一个用于按钮的不透明度)
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'),
),
)
]
),
)
),
]
)
);
}
}
如果您不关心animationController而只想制作一次动画(可能是在第一次显示页面时),您可以使用一些扩展ImplicitAnimations的小部件,这个类会为您完成所有动画,而无需您创建控制器,只需通过 setState 并更改它们的值
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(),
),
)
]
)
),
),
]
)
);
}
}
这种小部件也有一个名为 onEnd 的 VoidCallback,所以如果你想制作一些动画,比如说移动图像,直到完成更改不透明度,只需像这样更改它
_setAnimation() => setState((){
//don't change opacity here
align = !align;
});
然后在 AnimatedAlign 的 onEnd 中
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
),