Android Flutter实现仿闲鱼动画效果

作者:天选的打工人 时间:2023-07-15 15:32:47 

前言

目前正在做的项目,为了增加用户的体验度,准备增加一些动画效果,其中底部栏中间按钮的点击事件参考了闲鱼的动效,便在此基础上仿写了该动效,并增加了一些新的效果。

动效

闲鱼动效

Android Flutter实现仿闲鱼动画效果

仿写效果

Android Flutter实现仿闲鱼动画效果

思路

根据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,仿闲鱼,动画
0
投稿

猜你喜欢

  • Unity中EventTrigger的几种使用操作

    2022-01-15 06:54:37
  • 简单了解Java方法的定义和使用实现详解

    2023-10-30 16:12:46
  • java中对象调用成员变量与成员实例方法

    2023-08-04 11:42:49
  • java的三种随机数生成方式

    2022-03-06 13:43:57
  • Java表单重复提交的避免方法

    2022-03-03 10:06:06
  • Android控件CardView实现卡片效果

    2023-09-05 17:01:16
  • c# 使用异步编程的方法

    2023-09-29 02:27:32
  • Java如何实现对称加密

    2021-07-01 09:29:11
  • 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
  • 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
  • 使用Android studio3.6的java api方式调用opencv

    2023-10-10 17:16:38
  • C#多线程编程Task用法详解

    2021-10-27 14:26:10
  • 浅析Spring Boot单体应用熔断技术的使用

    2022-05-10 02:37:08
  • java控制台输出百分比进度条示例

    2023-08-21 22:58:20
  • C#实现字符串与图片的Base64编码转换操作示例

    2021-07-06 14:29:21
  • asp之家 软件编程 m.aspxhome.com