在flutter中,咱们能够经过 AnimationController 及各类 Animation 搭配使用的方式去实现 Widget 的动画。git
实现的方式也很是方便,经过flutter内置好的模版代码,在你建立的dart文件中输入 sta
便可建立出基本的动画模版类。github
那么,咱们能够经过这样的Widget组合方式,实现出怎样的动画呢?bash
接下来,咱们就以上面的动画为例子,讲一讲Widget强大的组合性!dom
由简到难,咱们依次开始组合出上面的效果。ide
晴天动画是最简单的,就是一个太阳360度不停旋转的效果学习
首先,经过模版代码 sta
建立出一个 WeatherSunny 类,初始化的 controller 和 animation 分别以下动画
AnimationController _controller;
Animation _animation;
@override
void initState() {
_controller = AnimationController(
vsync: this,
duration: Duration(seconds: 60),
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
...
}
复制代码
为了达到太阳不停旋转的效果,咱们须要把动画设置成循环的,因此须要监听它的状态ui
@override
void initState() {
...
_controller.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_controller.reset();
_controller.forward();
}
});
_controller.forward();
super.initState();
}
复制代码
因为动画须要进行Widget的刷新,因此咱们一般须要进行下面的操做:this
_controller.addListener((){
setState(() {});
});
复制代码
可是对于复杂度不高的动画,咱们可使用 AnimatedBuilder
去下降代码行数,因此在这里上面的监听刷新就没有必要了spa
而后是将 Animation 应用在 Widget 上
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return Container(
decoration: BoxDecoration(border: Border.all()),
child: Transform.rotate(
angle: pi * 2 * _animation.value * 5,
child: child,
),
);
},
child: Icon(
Icons.wb_sunny,
size: widget.sunnySize,
color: widget.sunColor,
),
);
}
复制代码
这里的太阳其实就是flutter默认提供的Icon,咱们让它每60s旋转 360 * 5 的度数,也就是每60s 转5圈。
到这里也许有同窗会问,为何不将 Duration 设置成12s,旋转度数设置成 360 ,效果不是同样吗?
效果确实同样,不过灵活度是不同的,等你实际操做一遍就能够体会到了。
晴天动画很是简单,实际上就是 旋转动画 + Icon 的组合
那么阴天动画如何实现呢,应该不少同窗已经知道了,就是 晴天动画 + Stack 的组合
首先咱们将以前的 WeatherSunny 封装好,让它能够从外部传入某些参数
WeatherSunny({
this.sunnySize = 100,
this.sunColor = Colors.orange,
...
})
复制代码
而后咱们建立一个 WeatherCloudy 去实现阴天动画,这里的阴天动画不须要额外的动画操做,因此不用将其建立成 StatefulWidget
@override
Widget build(BuildContext context) {
...
return Container(
width: width,
height: height,
child: Stack(
children: <Widget>[
Positioned(
left: sunOrigin.dx + cloudSize / 6,
top: sunOrigin.dy - cloudSize / 6,
child: WeatherSunny(
sunnySize: sunSize,
sunColor: sunColor,
),
),
Positioned(
left: cloudOrigin.dx,
top: cloudOrigin.dy,
child: Icon(
Icons.cloud,
size: cloudSize,
color: cloudColor,
),
),
],
),
);
}
复制代码
上面省去了不少细节代码,能够看到阴天的动画就是经过 Stack 组合 晴天动画 与另一个 云朵Icon,只不过咱们须要计算各个对象的相对坐标
落雨的动画稍微要复杂一些,由于雨点的生成都是随机的,因此须要使用到 Random()
在实现以前能够先思考一下,雨点是用什么去实现的?
也许有小伙伴早就知道了,就是经过 Container
去实现的雨点
Container(
width: randomWidth,
height: randomHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(randomWidth / 2)),
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white, Theme.of(context).primaryColor,
])),
)
复制代码
Container能够实现的效果很丰富,冒充雨点也是不在话下
接下来,就是如何展现出这么多的雨点。
显然,是经过 Stack + N个Position 的结合方式
咱们能够建立出随机数量的 Container 雨点展现,而后在 Position 中设置他们的随机坐标
//雨滴随机大小
final randomWidth = Random().nextDouble() * width / 50 + 1;
final randomHeight = Random().nextDouble() * height / 10;
//雨滴随机坐标
double randomL = Random().nextDouble() * width - randomWidth;
double randomT = Random().nextDouble() * height + randomHeight;
复制代码
不过又有一个问题来了,如何实现雨滴动画无限向下移动呢?
首先确定是须要让动画无限循环的
_controller.reset();
_controller.forward();
复制代码
让雨滴移动经过 Transform.translate 便可
Transform.translate(
offset: Offset(
0,
_animation.value * widget.droppingHeight,
),
child: child,
),
);
复制代码
实际上的动画应该上这个样子
因此还剩下一个问题,如何保证雨滴不出边界?
这里就须要用到另外一个控件 ClipRect
经过 ClipRect 的 clipper 属性,咱们能够对显示区域进行限制,接下来自定义一个 CustomClipper
class CustomRect extends CustomClipper<Rect> {
@override
Rect getClip(Size size) {
Rect rect = Rect.fromLTRB(0.0, 0.0, size.width, size.height);
return rect;
}
@override
bool shouldReclip(CustomRect oldClipper) {
return false;
}
}
复制代码
这样,咱们就能够把显示内容限制在 rect 的范围内
大概的代码以下
Widget build(BuildContext context) {
final children =
getDroppingWidget(widget.droppingHeight, widget.droppingWidth, context);
return Container(
width: widget.droppingWidth,
height: widget.droppingHeight,
decoration: BoxDecoration(border: Border.all()),
child: AnimatedBuilder(
animation: _animation,
builder: (ctx, child) {
return ClipRect(
clipper: CustomRect(),
child: Transform.translate(
offset: Offset(
0,
_animation.value * widget.droppingHeight,
),
child: child,
),
);
},
child: Stack(
children: [
Transform.translate(
offset: Offset(0, -widget.droppingHeight),
child: Stack(
children: children,
),
),
Stack(
children: children,
),
],
),
),
);
}
复制代码
下雪的动画与下雨的动画是同样的,只是将实现 雨滴 的Widget替换为 飘雪 的Widget
Container(
width: width,
height: width,
decoration: BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Colors.white,
Theme.of(context).primaryColor,
])),
);
复制代码
最后还有 雨雪 + 云 的动画,具体实现方式与 晴 + 云 的效果是差很少的,只是须要进行位置的计算有所不一样
那么,经过 widget 组合实现一些动画效果就到此为止,能够看到在flutter 中 万物基于widget 绝非空口无凭,
demo地址以下:
(ps:demo中我将控件进行了封装,能够很方便的调用,原本是打算写成一个dart package的,后来以为效果比较简单,仍是用做学习素材最为合适!
封装后,经过 droppingType 参数来控制降低的是与仍是雪,经过 droppingLevel 参数控制雨雪的数量。 也能够经过 droppingWidget 参数来自定义下落的控件。 )