介绍
来自 仿网易云音乐App
预览图
分析
经过观察,可以发现,这是一个相对简单的动画,由两部分组成:
logo:自身没有任何动画效果
波纹:向外扩散的淡出波纹,最多有两条
这样,我们实现的话可以借助Stack来做。
实现
首先我们创建一个LogoWidget,它包含整个我们要做的功能
代码是基于Bedrock框架所写,widget的书写和创建你可能不太熟悉,可以点击下方链接了解
代码较多时,我会将说明写在注释里,方便联系阅读
LogoWidget
我们先创建3个变量
final List<Widget> list = [];//用于存放widget,是stack的children
Timer timer; //计时器,用于控制波纹 add/remove
Image logo; //网易云的logo
接下来我们对它们进行初始化
@override
void initState() {
logo = Image.asset(ImageHelper.wrapAssetsIcon('logo'),width: getWidthPx(150),height: getWidthPx(150),);
super.initState();
list.add(logo);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
initTimer();
});
}
void initTimer(){
timer = Timer.periodic(Duration(seconds: 2), (timer) {
if(mounted){
///理论上讲 第二个总是先完成的
list.add(WaveWidget((index){
list.removeAt(1);//回调用于移除淡出的波纹
}).generateWidget());
setState(() {
});
}else{
timer.cancel();
}
});
}
initState里面的代码简单,不做赘述,我们看一下这个方法:
initTimer()
可以看到里面启动了一个timer,每2秒回执行以下第二个参数(Function)。
因为我们每2秒显示一个波纹,且波纹是会淡出的,所以理论上list内部应该只有三个widget,即要移除淡出完的widget
1,logo 2, 波纹1号 3,波纹2号
因为list是有序的,所以我们应该移除 index=1的元素,具体什么时候移除,我们交给了 waveWidget来处理。
WaveWidget
首先我们声明一个回调
typedef AnimationCallback = void Function(int index);//这个index貌似多余了
变量:
final AnimationCallback animateDone;
WaveWidget( this.animateDone);
AnimationController controller;
Animation first;//名字起2了...
//波纹的透明度
double opacity = 0.8;
很简单,不做赘述,我们初始化一下它们:
@override
void initState() {
controller = AnimationController(duration: Duration(seconds: 3),vsync: this);
//我们确定了动画的最大值和最小值 即 圆的最小半径和最大半径
first = Tween<double>(begin: getWidthPx(180),end: getWidthPx(360)).animate(controller);
super.initState();
//对动画进行监听
first.addListener(() {
//我们根据动画的执行进度,算出对应的 不透明度, 最终是0,即完全透明
opacity = (1-first.value/getWidthPx(360)).clamp(0.0, 1.0);//这里要控制一下阈值,因为会超出的
setState(() {
});
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
///开始动画
controller.forward();
});
}
接下来我们看一下 布局
@override
Widget build(BuildContext context) {
return Container(
width: first.value,height: first.value,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white.withOpacity(opacity),width: getWidthPx(2))
),
);
}
很简单,创建一个圆圈,半径与animation.value进行绑定,这样随着动画的执行,圆圈就会越变越大,颜色越来越淡。
至此整个动画就完成了,下面是完整代码
完整代码
class LogoWidget extends WidgetState with SingleTickerProviderStateMixin{
final List<Widget> list = [];
Timer timer;
Image logo;
@override
void initState() {
logo = Image.asset(ImageHelper.wrapAssetsIcon('logo'),width: getWidthPx(150),height: getWidthPx(150),);
super.initState();
list.add(logo);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
initTimer();
});
}
void initTimer(){
timer = Timer.periodic(Duration(seconds: 2), (timer) {
if(mounted){
///理论上讲 第二个总是先完成的
list.add(WaveWidget((index){
list.removeAt(1);
}).generateWidget());
setState(() {
});
}else{
timer.cancel();
}
});
}
@override
Widget build(BuildContext context) {
return Container(
width: getWidthPx(500),height: getWidthPx(500),
child: Stack(
alignment: Alignment.center,
children: list,
),
);
}
}
typedef AnimationCallback = void Function(int index);
class WaveWidget extends WidgetState with SingleTickerProviderStateMixin{
final AnimationCallback animateDone;
WaveWidget( this.animateDone);
AnimationController controller;
Animation first;
double opacity = 0.8;
@override
void initState() {
controller = AnimationController(duration: Duration(seconds: 3),vsync: this);
first = Tween<double>(begin: getWidthPx(180),end: getWidthPx(360)).animate(controller);
super.initState();
first.addListener(() {
opacity = (1-first.value/getWidthPx(360)).clamp(0.0, 1.0);
setState(() {
});
});
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
controller.forward();
});
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
width: first.value,height: first.value,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.white.withOpacity(opacity),width: getWidthPx(2))
),
);
}
}
Demo
内部搜索即可