Flutter 隐式动画
官方文档: flutter.dev/docs/codela…
一、什么是隐式动画
我们平时使用的 AnimatonController 来控制的动画,需要我们指定动画的运行时间,动画的运动曲线,并手动控制动画的开始和结束,这其实是显式动画,与之对应的就是隐式动画。顾名思义,隐式动画其实不需要我们对动画做太多的干预,直接使用 Flutter 内部定义好的动画组件,就可以实现简单的动画效果。
常见的隐式动画组件有 AnimatedOpacity , AnimatedContainer, AnimatedPadding, AnimatedPositioned, AnimatedSwitcher 以及 AnimatedAlign 等,通过这些组件的名字也能看出来这些组件的作用,接下来简单介绍一下如何使用隐式动画组件。
二、AnimatedOpacity 实现渐隐效果
1、AnimatedOpacity 介绍
AnimatedOpacity 这个 Widget 可以实现透明度变化的动画效果。
先看一下 AnimatedOpacity 的构造函数。
const AnimatedOpacity({
Key key,
this.child,
@required this.opacity,
Curve curve = Curves.linear,
@required Duration duration,
VoidCallback onEnd,
this.alwaysIncludeSemantics = false,
}) : assert(opacity != null && opacity >= 0.0 && opacity <= 1.0),
super(key: key, curve: curve, duration: duration, onEnd: onEnd);
这些参数基本上看一眼就知道是干嘛的了。
- child : 子控件,也就是动画的作用对象
- opacity : 指定透明度
- curve: 动画的变换曲线,默认是线性变换
- duration: 动画时间
- onEnd : 结束回调
可以看得出,和我们平时正常使用 Widget 其实没有任何区别,但是却大大简化的动画的使用。
2、示例
class FadeInDemo extends StatefulWidget {
_FadeInDemoState createState() => _FadeInDemoState();
}
class _FadeInDemoState extends State<FadeInDemo> {
double opacity = 0.0;
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.network(
'https://picsum.photos/250?image=9',
),
MaterialButton(
child: Text(
'Show Details',
style: TextStyle(color: Colors.blueAccent),
),
onPressed: () => setState(() {
opacity = 1;
}),
),
AnimatedOpacity(
duration: Duration(seconds: 3),
opacity: opacity,
child: Column(
children: <Widget>[
Text('Type: Owl'),
Text('Age: 39'),
Text('Employment: None'),
],
),
)
]);
}
}
使用 AnimatedOpcacity 需要指定初始的透明度值, 0 为不可见,当点击按钮,设置为 1,同时 setState 将会触发动画效果。duration 指定了动画时间为 3s 。
效果:
三、 AnimatedContainer 实现多属性改变动画
1、AnimatedContainer 介绍
AnimatedContainer 这个 Widget 和 Container 一样,是同一个可以控制多个属性(如 margin、borderRaidu、color)的 widget ,同时还能加上动画。
构造函数如下:
AnimatedContainer({
Key key,
this.alignment,
this.padding,
Color color,
Decoration decoration,
this.foregroundDecoration,
double width,
double height,
BoxConstraints constraints,
this.margin,
this.transform,
this.child,
Curve curve = Curves.linear,
@required Duration duration,
VoidCallback onEnd,
}) : assert(margin == null || margin.isNonNegative),
assert(padding == null || padding.isNonNegative),
assert(decoration == null || decoration.debugAssertIsValid()),
assert(constraints == null || constraints.debugAssertIsValid()),
assert(color == null || decoration == null,
'Cannot provide both a color and a decoration\n'
'The color argument is just a shorthand for "decoration: BoxDecoration(color: color)".'
),
decoration = decoration ?? (color != null ? BoxDecoration(color: color) : null),
constraints =
(width != null || height != null)
? constraints?.tighten(width: width, height: height)
?? BoxConstraints.tightFor(width: width, height: height)
: constraints,
super(key: key, curve: curve, duration: duration, onEnd: onEnd);
和正常的 Container 使用没有什么区别,就是加上了动画相关的几个属性。
2、示例
const _duration = Duration(milliseconds: 400);
double randomBorderRadius() {
return Random().nextDouble() * 64;
}
double randomMargin() {
return Random().nextDouble() * 64;
}
Color randomColor() {
return Color(0xFFFFFFFF & Random().nextInt(0xFFFFFFFF));
}
class AnimatedContainerDemo extends StatefulWidget {
_AnimatedContainerDemoState createState() => _AnimatedContainerDemoState();
}
class _AnimatedContainerDemoState extends State<AnimatedContainerDemo> {
Color color;
double borderRadius;
double margin;
@override
void initState() {
super.initState();
color = Colors.deepPurple;
borderRadius = randomBorderRadius();
margin = randomMargin();
}
void change() {
setState(() {
color = randomColor();
borderRadius = randomBorderRadius();
margin = randomMargin();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
width: 128,
height: 128,
child: AnimatedContainer(
margin: EdgeInsets.all(margin),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(borderRadius),
),
duration: _duration,
curve: Curves.easeInOutBack,
),
),
MaterialButton(
color: Theme.of(context).primaryColor,
child: Text(
'change',
style: TextStyle(color: Colors.white),
),
onPressed: () => change(),
),
],
),
),
);
}
}
上面示例的代码可以动态的改变 Container 的 margin,borderRaidus 以及 color 属性。 效果:
四、AnimatedSwitcher 实现切换动画效果
AnimatedSwitcher 的详细的介绍和原理可以参考 Flutter 中文网—通用“动画切换”组件
1、AnimatedSwitcher 介绍
AnimatedSwitcher 可以同时对其新、旧子元素添加显示、隐藏动画。也就是说在AnimatedSwitcher的子元素发生变化时,会对其旧元素和新元素。 其构造函数如下:
const AnimatedSwitcher({
Key key,
this.child,
@required this.duration, // 新child显示动画时长
this.reverseDuration,// 旧child隐藏的动画时长
this.switchInCurve = Curves.linear, // 新child显示的动画曲线
this.switchOutCurve = Curves.linear,// 旧child隐藏的动画曲线
this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder, // 动画构建器
this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder, //布局构建器
})
当AnimatedSwitcher的child发生变化时(类型或Key不同),旧child会执行隐藏动画,新child会执行执行显示动画。究竟执行何种动画效果则由transitionBuilder参数决定,该参数接受一个AnimatedSwitcherTransitionBuilder类型的builder,定义如下:
typedef AnimatedSwitcherTransitionBuilder =
Widget Function(Widget child, Animation<double> animation);
该builder在AnimatedSwitcher的child切换时会分别对新、旧child绑定动画:
对旧child,绑定的动画会反向执行(reverse) 对新child,绑定的动画会正向指向(forward) 这样一下,便实现了对新、旧child的动画绑定。AnimatedSwitcher的默认值是AnimatedSwitcher.defaultTransitionBuilder :
Widget defaultTransitionBuilder(Widget child, Animation<double> animation) {
return FadeTransition(
opacity: animation,
child: child,
);
}
可以看到,返回了FadeTransition对象,也就是说默认情况,AnimatedSwitcher会对新旧child执行“渐隐”和“渐显”动画。
2、示例
class AnimatedSwitcherCounterRoute extends StatefulWidget {
const AnimatedSwitcherCounterRoute({Key key}) : super(key: key);
@override
_AnimatedSwitcherCounterRouteState createState() => _AnimatedSwitcherCounterRouteState();
}
class _AnimatedSwitcherCounterRouteState extends State<AnimatedSwitcherCounterRoute> {
int _count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
transitionBuilder: (Widget child, Animation<double> animation) {
///缩放动画
return ScaleTransition(child: child, scale: animation);
///上下左右平移动画
// return SlideTransitionX(
// child: child,
// direction: AxisDirection.up, //上入下出
// position: animation,
// );
},
child: Text(
'$_count',
//显示指定key,不同的key会被认为是不同的Text,这样才能执行动画
key: ValueKey<int>(_count),
style: Theme.of(context).textTheme.display1,
),
),
RaisedButton(
child: const Text('+1',),
onPressed: () {
setState(() {
_count += 1;
});
},
),
],
),
);
}
}
上面的 transitionBuilder 通过指定不同的返回值类型,可以控制不同的动画效果。
缩放:
平移:
推荐阅读
欢迎关注「Flutter 编程开发」微信公众号 。