Android Flutter实现仿闲鱼动画效果
作者:天选的打工人 时间:2023-07-15 15:32:47
前言
目前正在做的项目,为了增加用户的体验度,准备增加一些动画效果,其中底部栏中间按钮的点击事件参考了闲鱼的动效,便在此基础上仿写了该动效,并增加了一些新的效果。
动效
闲鱼动效
仿写效果
思路
根据UI的设计图,对每个模块设计好动画效果,本人主要设计了以下四个效果。
1、底部返回键旋转动画
底部返回按钮动画其实就是个旋转动画,利用Transform.rotate
设置angle
的值即可,这里使用了GetX来对angle进行动态控制。
//返回键旋转角度,初始旋转45度,使其初始样式为 +
var angle = (pi / 4).obs;
///关闭按钮旋转动画控制器
late final AnimationController closeController;
late final Animation<double> closeAnimation;
///返回键旋转动画
closeController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: provider,
);
///返回键旋转动画
closeController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: provider,
);
///页面渲染完才开始执行,不然第一次打开不会启动动画
WidgetsBinding.instance.addPostFrameCallback((duration) {
closeAnimation =
Tween(begin: pi / 4, end: pi / 2).animate(closeController)
..addListener(() {
angle.value = closeAnimation.value;
});
closeController.forward();
});
///关闭按钮点击事件
void close() {
///反转动画,并关闭页面
Future.delayed(
const Duration(milliseconds: 120), () {
Get.back();
});
closeController.reverse();
}
IconButton(
onPressed: null,
alignment: Alignment.center,
icon: Transform.rotate(
angle: controller.angle.value,
child: SvgPicture.asset(
"assets/user/ic-train-car-close.svg",
width: 18,
height: 18,
color: Colors.black,
),
))
2、底部四个栏目变速上移动画+渐变动画
四个栏目其实就是个平移动画,只不过闲鱼是四个栏目一起平移,而我选择了变速平移,这样视觉效果上会好一点。
//透明度变化
List<AnimationController> opacityControllerList = [];
//上移动画,由于每个栏目的移动速度不一样,需要用List保存四个AnimationController,
//如果想像闲鱼那种整体上移,则只用一个AnimationController即可。
List<AnimationController> offsetControllerList = [];
List<Animation<Offset>> offsetAnimationList = [];
//之所以用addIf,是因为项目中这几个栏目的显示是动态显示的,这里就直接写成true
Column(
children: []
..addIf(
true,
buildItem('assets/user/ic-train-nomal-car.webp',"学车加练","自主预约,快速拿证"))
..addIf(
true,
buildItem('assets/user/ic-train-fuuxn-car.webp',"有证复训","优质陪练,轻松驾车"))
..addIf(
true,
buildItem('assets/user/ic-train-jiaxun-car.webp',"模拟加训","考前加训,临考不惧"))
..addIf(
true,
buildItem('assets/user/ic-train-jiakao-car.webp',"驾考报名","快捷报名无门槛"))
..add(playWidget())
..addAll([
17.space,
]),
)
//仅仅是为了在offsetController全部初始化完后执行play()
Widget playWidget() {
//执行动画
play();
return Container();
}
int i = 0;
Widget buildItem(String img,String tab,String slogan) {
//由于底部栏目是动态显示的,需要在创建Widget时一同创建offsetController和offsetAnimation
i++;
AnimationController offsetController = AnimationController(
duration: Duration(milliseconds: 100 + i * 20),
vsync: this,
);
Animation<Offset> offsetAnimation = Tween<Offset>(
begin: const Offset(0, 2.5),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: offsetController,
// curve: Curves.easeInOutSine,
curve: const Cubic(0.12, 0.28, 0.48, 1),
));
AnimationController opacityController = AnimationController(
duration: const Duration(milliseconds: 500),
lowerBound: 0.2,
upperBound: 1.0,
vsync: this);
opacityControllerList.add(opacityController);
offsetControllerList.add(offsetController);
offsetAnimationList.add(offsetAnimation);
return SlideTransition(
position: offsetAnimation,
child: FadeTransition(
opacity: opacityController,
child: Container(
margin: EdgeInsets.only(bottom: 16),
height: 62,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(12)),
color: const Color(0xfffafafa)),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
24.space,
Image.asset(img, width: 44, height: 44),
12.space,
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(tab,
style: const TextStyle(
color: Color(0XFF000000),
fontSize: 16,
fontWeight: FontWeight.bold)),
Text(slogan,
style: const TextStyle(
color: Color(0XFF6e6e6e), fontSize: 12)),
]).expanded,
Image.asset("assets/user/ic-train-arrow.webp",
width: 44, height: 44),
17.space
])).inkWell(
onTap: () {},
delayMilliseconds: 50)),
);
}
//执行动画
void play() async {
for (int i = 0; i < offsetControllerList.length; i++) {
opacityControllerList[i].forward();
///栏目正序依次延迟(40 + 2 * i) * i的时间,曲线速率
Future.delayed(Duration(milliseconds: (40 + 2 * i) * i), () {
offsetControllerList[i]
.forward()
.whenComplete(() => offsetControllerList[i].stop());
});
}
}
///关闭按钮点击事件
void close() {
///反转动画,并关闭页面
Future.delayed(
const Duration(milliseconds: 120), () {
Get.back();
});
for (int i = offsetControllerList.length - 1; i >= 0; i--) {
///栏目倒叙依次延迟(40 + 2 * (offsetControllerList.length-1-i)) * (offsetControllerList.length-1-i))的时间
Future.delayed(
Duration(
milliseconds:
(40 + 2 * (offsetControllerList.length-1-i)) * (offsetControllerList.length-1-i)), () {
offsetControllerList[i].reverse();
});
}
opacityTopController.reverse();
}
3、中间图片渐变动画
渐变动画使用FadeTransition
即可。
///图片透明度渐变动画控制器
late final AnimationController imgController;
///图片透明度渐变动画
imgController = AnimationController(
duration: const Duration(milliseconds: 500),
lowerBound: 0.0,
upperBound: 1.0,
vsync: provider);
imgController.forward().whenComplete(() => imgController.stop());
///渐变过渡
FadeTransition(
opacity: imgController,
child:
Image.asset("assets/user/ic-traincar-guide.webp"),
),
///关闭按钮点击事件
void close() {
imgController.reverse();
}
4、顶部文案渐变动画+下移动画
///顶部标题下移动画控制器
late final AnimationController offsetTopController;
late final Animation<Offset> offsetTopAnimation;
///顶部标题渐变动画控制器
late final AnimationController opacityTopController;
///顶部标题上移动画
offsetTopController = AnimationController(
duration: const Duration(milliseconds: 300),
vsync: provider,
);
offsetTopController
.forward()
.whenComplete(() => offsetTopController.stop());
offsetTopAnimation = Tween<Offset>(
begin: const Offset(0, -0.8),
end: const Offset(0, 0),
).animate(CurvedAnimation(
parent: offsetTopController,
curve: Curves.easeInOutCubic,
));
offsetTopController
.forward()
.whenComplete(() => offsetTopController.stop());
//UI
SlideTransition(
position: offsetTopAnimation,
child: FadeTransition(
opacity: opacityTopController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
80.space,
const Text(
'练车指南',
style: TextStyle(
color: Color(0XFF141414),
fontSize: 32,
fontWeight: FontWeight.w800,
),
),
2.space,
const Text('易练只为您提供优质教练,为您的安全保驾护航',
style: TextStyle(
color: Color(0XFF141414),
fontSize: 15)),
],
))),
///关闭按钮点击事件
void close() {
offsetTopController.reverse();
opacityTopController.reverse();
}
5、注销动画
最后,在关闭页面的时候不要忘记注销动画。
///关闭时注销动画
void dispose() {
for (int i = offsetControllerList.length - 1; i > 0; i--) {
offsetControllerList[i].dispose();
}
offsetTopController.dispose();
opacityTopController.dispose();
imgController.dispose();
closeController.dispose();
}
来源:https://juejin.cn/post/7202537103933964346
标签:Android,Flutter,仿闲鱼,动画
![](/images/zang.png)
![](/images/jiucuo.png)
猜你喜欢
Unity中EventTrigger的几种使用操作
2022-01-15 06:54:37
![](https://img.aspxhome.com/file/2023/6/88426_0s.jpg)
简单了解Java方法的定义和使用实现详解
2023-10-30 16:12:46
java中对象调用成员变量与成员实例方法
2023-08-04 11:42:49
![](https://img.aspxhome.com/file/2023/4/71944_0s.png)
java的三种随机数生成方式
2022-03-06 13:43:57
Java表单重复提交的避免方法
2022-03-03 10:06:06
Android控件CardView实现卡片效果
2023-09-05 17:01:16
![](https://img.aspxhome.com/file/2023/8/126718_0s.jpg)
c# 使用异步编程的方法
2023-09-29 02:27:32
![](https://img.aspxhome.com/file/2023/7/113777_0s.png)
Java如何实现对称加密
2021-07-01 09:29:11
![](https://img.aspxhome.com/file/2023/6/83126_0s.png)
Android ListView position详解及实例代码
2023-10-29 03:33:15
c#自带缓存使用方法 c#移除清理缓存
2021-09-07 10:20:15
Java开发druid数据连接池maven方式简易配置流程示例
2021-05-26 14:57:23
Android植物大战僵尸小游戏
2023-08-05 21:27:04
![](https://img.aspxhome.com/file/2023/7/86017_0s.png)
Mybatis如何解决sql中like通配符模糊匹配问题
2023-12-22 19:39:52
c# 实现获取汉字十六进制Unicode编码字符串的实例
2023-03-21 11:22:35
Unity3D实现NavMesh导航网格寻路
2021-07-29 05:38:18
![](https://img.aspxhome.com/file/2023/7/106457_0s.jpg)
使用Android studio3.6的java api方式调用opencv
2023-10-10 17:16:38
![](https://img.aspxhome.com/file/2023/1/138511_0s.png)
C#多线程编程Task用法详解
2021-10-27 14:26:10
![](https://img.aspxhome.com/file/2023/2/96422_0s.jpg)
浅析Spring Boot单体应用熔断技术的使用
2022-05-10 02:37:08
![](https://img.aspxhome.com/file/2023/5/75435_0s.png)
java控制台输出百分比进度条示例
2023-08-21 22:58:20
C#实现字符串与图片的Base64编码转换操作示例
2021-07-06 14:29:21