Flutter实现动画列表AnimateListView

news2024/9/23 16:03:25

由于业务需要,在打开列表时,列表项需要一个从右边飞入的动画效果,故封装一个专门可以执行动画的列表组件,可以自定义自己的动画,内置有水平滑动,缩放等简单动画。花里胡哨的动画效果由你自己来定制吧。

功能:

1.可自定义动画。

2.内置水平滑动和缩放动画。

演示:

代码:

import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';

///可设置动画的列表。
///内部使用的是[SingleChildScrollView]实现,
///需要外层设置定义可约束高度。
class KqAnimateListView<T> extends StatefulWidget {
  ///数据
  final List<T> data;

  ///列表
  final Widget Function(T t) item;

  ///动画自定义
  final IAnimate? animate;

  ///是否需要每次刷新时都执行动画
  final bool isNeedFlashEveryTime;

  const KqAnimateListView({
    super.key,
    required this.item,
    required this.data,
    this.animate,
    this.isNeedFlashEveryTime = false,
  });

  @override
  State<StatefulWidget> createState() => _KqAnimateListViewState<T>();
}

class _KqAnimateListViewState<T> extends State<KqAnimateListView<T>>
    with TickerProviderStateMixin {
  late IAnimate animate;
  late AnimationController controller;
  late Animation animation;

  @override
  void initState() {
    super.initState();
    animate = widget.animate ?? ScaleAnimate();
    animate.init(widget.data.length);
    controller = animate.getAnimationController(this);
    animation = animate.getAnimation(controller, this);
    //启动动画(正向执行)
    controller.forward();
  }

  @override
  void didUpdateWidget(covariant KqAnimateListView<T> oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isNeedFlashEveryTime) {
      animate = widget.animate ?? ScaleAnimate();
      animate.init(widget.data.length);
      controller = animate.getAnimationController(this);
      animation = animate.getAnimation(controller, this);
      //启动动画(正向执行)
      controller.forward();
    }
  }

  @override
  Widget build(BuildContext context) {
    return SizedBox.expand(
      child: SingleChildScrollView(
        child: Column(
          children: _getChildren(context),
        ),
      ),
    );
  }

  List<Widget> _getChildren(BuildContext context) {
    List<Widget> list = [];
    for (int i = 0; i < widget.data.length; i++) {
      Widget child = widget.item.call(widget.data[i]);
      list.add(
        animate.animate(context, i, child, animation, controller),
      );
    }
    return list;
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }
}

///动画抽象类。
///实现该类,定制自己的列表动画。
abstract class IAnimate {
  ///初始化
  ///[length] 列表长度
  void init(int length);

  ///获取AnimationController
  AnimationController getAnimationController(TickerProvider provider);

  ///获取Animation
  Animation getAnimation(
      AnimationController controller, State<StatefulWidget> state);

  ///定制自己的动画,每一个item都会调用到animate,
  ///当需要差异动画时需要根据[index]计算不同item的动画时机
  ///[widget] 执行动画之后的widget
  ///[index] 列表的item的index
  Widget animate(
    BuildContext context,
    int index,
    Widget widget,
    Animation animation,
    AnimationController controller,
  );
}

///水平移动动画
class HorizontalAnimate extends IAnimate {
  ///动画执行时长比例
  static double rate = 0.4;

  ///单个动画执行长度
  static double baseGap = 1000.0;

  ///两个个动画执行间隔长度
  static double twoGap = 200.0;

  ///动画执行的总长度,根据列表长度动态计算值
  late double gap;

  ///动画是从左边还是右边开始执行
  HorizontalAnimateDirection direction;

  HorizontalAnimate({this.direction = HorizontalAnimateDirection.right});

  @override
  void init(int length) {
    gap = length * twoGap + baseGap;
  }

  @override
  Widget animate(BuildContext context, int index, Widget widget,
      Animation animation, AnimationController controller) {
    double width = context.width;

    ///范围0->1
    double mix = (animation.value - twoGap * index) / baseGap;
    if (mix > 1) {
      mix = 1;
    }
    double left = width * (1 - mix);
    return Container(
      transform: Matrix4.translationValues(
          direction == HorizontalAnimateDirection.left ? -left : left, 0, 0),
      child: widget,
    );
  }

  @override
  AnimationController getAnimationController(TickerProvider provider) {
    return AnimationController(
        duration: Duration(milliseconds: (gap * rate).toInt()),
        vsync: provider);
  }

  @override
  Animation getAnimation(
      AnimationController controller, State<StatefulWidget> state) {
    return Tween(begin: 0.0, end: gap).animate(controller)
      ..addListener(() {
        if (state.mounted) {
          state.setState(() {});
        }
      });
  }
}

enum HorizontalAnimateDirection {
  ///左边
  left,

  ///右边
  right;
}

///缩放动画
class ScaleAnimate extends IAnimate {
  ///动画执行时长比例
  static double rate = 0.4;

  ///单个动画执行长度
  static double baseGap = 1000.0;

  ///两个个动画执行间隔长度
  static double twoGap = 200.0;

  ///动画执行的总长度,根据列表长度动态计算值
  late double gap;

  @override
  Widget animate(BuildContext context, int index, Widget widget,
      Animation animation, AnimationController controller) {
    double width = context.width;

    ///范围0.5->1
    double mix = ((animation.value - twoGap * index) / baseGap + 1) / 2;
    if (mix > 1) {
      mix = 1;
    }
    double widthMix = width * mix;
    return SizedBox(
      width: max(0, widthMix),
      child: widget,
    );
  }

  @override
  AnimationController getAnimationController(TickerProvider provider) {
    return AnimationController(
        duration: Duration(milliseconds: (gap * rate).toInt()),
        vsync: provider);
  }

  @override
  Animation getAnimation(
      AnimationController controller, State<StatefulWidget> state) {
    return Tween(begin: 0.0, end: gap).animate(controller)
      ..addListener(() {
        if (state.mounted) {
          state.setState(() {});
        }
      });
  }

  @override
  void init(int length) {
    gap = length * twoGap + baseGap;
  }
}

 使用:

                KqAnimateListView(
                    data: const [
                      "Test1",
                      "Test2",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3",
                      "Test3"
                    ],
                    item: (t) {
                      return Container(
                        width: double.infinity,
                        height: 50,
                        color: Colors.redAccent,
                        margin: EdgeInsets.only(top: 10.r),
                        child: Text(t),
                      );
                    },
                    animate: type == 0
                        ? HorizontalAnimate(
                            direction: HorizontalAnimateDirection.left)
                        : type == 1
                            ? HorizontalAnimate(
                                direction: HorizontalAnimateDirection.right)
                            : ScaleAnimate(),
                    isNeedFlashEveryTime: true,
                  )

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/922369.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

在本地搭建Jellyfin影音服务器,支持公网远程访问影音库的方法分享

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

爆火「视频版ControlNet」开源了!靠提示词精准换画风,全华人团队出品

“视频版ControlNet”来了&#xff01; 让蓝衣战神秒变迪士尼公举&#xff1a; 视频处理前后&#xff0c;除了画风以外&#xff0c;其他都不更改。 女孩说话的口型都保持一致。 正在插剑的姜文&#xff0c;也能“下一秒”变猩球崛起了。 这就是由全华人团队打造的最新视频处理…

四川玖璨电子商务有限公司:怎么拉升抖店体验分

抖音是中国最受欢迎的短视频分享平台之一&#xff0c;而在抖音上开设自己的抖店成为许多电商从业者的选择。为了提高抖店的曝光度和用户体验&#xff0c;抖店体验分成为了一个重要的指标。在本文中&#xff0c;小编将从两个方面来讨论怎么拉升抖店体验分&#xff0c;包括影响因…

你真的理解 shell 中的 $?

$? 是一个特殊变量&#xff0c;用于获取上一个命令或函数的退出状态码&#xff0c;这里要注意的是状态码和返回值是不同的概念。在函数中我们可以使用返回标志 return&#xff0c;return 的返回值会作为退出状态码供 $? 截取&#xff0c;当函数没有 return 返回值时&#xff…

【Jellyfin影音服务器】 本地部署公网远程影音库

文章目录 1. 前言2. Jellyfin服务网站搭建2.1. Jellyfin下载和安装2.2. Jellyfin网页测试 3.本地网页发布3.1 cpolar的安装和注册3.2 Cpolar云端设置3.3 Cpolar本地设置 4.公网访问测试5. 结语 1. 前言 随着移动智能设备的普及&#xff0c;各种各样的使用需求也被开发出来&…

程序员至少要掌握几种编程语言?来看看这些编程语言的优势和用途

你可能听说过&#xff0c;作为一个程序员&#xff0c;你要学习很多种编程语言&#xff0c;才能应对各种不同的项目和需求。那么&#xff0c;程序员最少要掌握几种编程语言呢&#xff1f;其实&#xff0c;这个问题没有一个确定的答案&#xff0c;因为不同的编程语言有不同的优势…

若依vue打印的简单方法

像我们后端程序员做前端的话,有时候真不需要知道什么原理,直接塞就好了 我们选用基于hiprint 的vue-plugin-hiprint来打印 目的是为了实现点击某些行的数据,然后点击某个按钮直接弹出下面的打印 此链接 大佬是原创,我拿来总结梳理一下 插件进阶功能请移步: 链接 插件模板制作页…

直播购物系统开发定制:融合技术与个性化的未来购物体验

在数字化时代&#xff0c;直播购物已经成为了商业领域的一大趋势。而通过直播购物系统开发定制&#xff0c;商家能够更好地满足消费者的个性化需求&#xff0c;为购物体验带来前所未有的变革。在本文中&#xff0c;我们将探讨如何通过技术手段来实现直播购物系统的开发定制&…

Layer Normalization(层规范化)

详细内容在这篇论文&#xff1a;Layer Normalization 训练深度神经网络需要大量的计算&#xff0c;减少计算时间的一个有效方法是规范化神经元的活动&#xff0c;例如批量规范化BN&#xff08;batch normalization&#xff09;技术&#xff0c;然而&#xff0c;批量规范化对小批…

【若依管理系统 权限控制】

1.在菜单管理添加按钮权限&#xff0c;如图 2.在角色管理里面加上菜单&#xff0c;如图 3.前端控制&#xff1a; 4.后端控制&#xff1a; 5.重启后台后&#xff0c;重新登录用户。

无涯教程-Python - Dictionary(字典)

每个键都由一个冒号(:)与其值分隔&#xff0c;各元素之间以逗号分隔&#xff0c;并且整个内容都用花括号括起来。一个没有任何元素的空字典用两个大括号书写&#xff0c;如:{}。 键在字典中是唯一的&#xff0c;而值可能不是。字典的值可以是任何类型&#xff0c;但是键必须是…

【机器学习7】特征缩放

特征缩放 &#x1f340;特征缩放的重要性&#x1f331;归一化&#x1f331;标准化&#x1f331;更高级的缩放方法&#x1f338;导入数据集&将数据集划分为训练集和测试集&#x1f338;Sklearn-Learn算法实现归一化&#x1f338;Sklearn-Learn算法实现标准化 &#x1f340;特…

Activity 的启动流程(Android 13)

Activity 的启动过程分为两种&#xff1a;一种是普通 Activity 的启动过程&#xff0c;另一种是根 Activity 的启动过程。普通 Activity 指的是除应用程序启动的第一个 Activity 之外的其他 Activity。根 Activity 指的是应用程序启动的第一个 Activity&#xff0c;因此&#x…

春秋云镜 :CVE-2020-21650(MyuCMS后台rce)

一、题目 靶标介绍&#xff1a; MyuCMS开源内容管理系统,采用ThinkPHP开发而成的社区商城聚合&#xff0c;插件&#xff0c;模板&#xff0c;轻便快捷容易扩展 其2.2版本中admin.php/config/add方法存在任意命令执行漏洞. 进入题目&#xff1a; exp&#xff1a; url/index.p…

计算机网络 QA

DNS 的解析过程 浏览器缓存。当用户通过浏览器访问某域名时&#xff0c;浏览器首先会在自己的缓存中查找是否有该域名对应的 IP 地址&#xff08;曾经访问过该域名并且没有清空缓存&#xff09;系统缓存。当浏览器缓存中无域名对应的 IP 地址时&#xff0c;会自动检测用户计算机…

opencv 进阶20-随机森林示例

OpenCV中的随机森林是一种强大的机器学习算法&#xff0c;旨在解决分类和回归问题。随机森林使用多个决策树来进行预测&#xff0c;每个决策树都是由随机选择的样本和特征组成的。在分类问题中&#xff0c;随机森林通过投票来确定最终的类别&#xff1b;在回归问题中&#xff0…

Blazor组件化开发心得:Blazor开发套路

文章目录 前言Blazor开发套路文件分类示意图 如何分工 前言 接触Blazor也快有一个多月了&#xff0c;了解Blazor之后发现确实是个好东西&#xff0c;开发速度太快了&#xff0c;前端直接拿数据&#xff0c;通过SSR保证安全。但是有一个问题&#xff0c;服务器能承受多大的压力…

Spark项目Java和Scala混合打包编译

文章目录 项目结构Pom完整文件编译查看 实际开发用有时候引用自己写的一些java工具类&#xff0c;但是整个项目是scala开发的spark程序&#xff0c;在项目打包时需要考虑到java和scala混合在一起编译。 今天看到之前很久之前写的一些打包编译文章&#xff0c;发现很多地方不太对…

【资料分享】基于NXP i.MX 8M Plus的异构多核核心板规格书

1 核心板简介 创龙科技SOM-TLIMX8MP是一款基于NXP i.MX 8M Plus的四核ARM Cortex-A53 单核ARM Cortex-M7异构多核处理器设计的高端工业核心板&#xff0c;ARM Cortex-A53(64-bit)主处理单元主频高达1.6GHz&#xff0c;ARM Cortex-M7实时处理单元主频高达800MHz。处理器采用14…

【Java】代理实现重试功能

在有些调用http请求功能中&#xff0c;会因为网络的抖动&#xff0c;使得网络不稳定。这时需要一个功能来实现重试。 下面介绍使用JDK的代理功能和Cglib来实现简单的代理重试。 JDK的代理需要被代理的类实现接口&#xff0c;下面的newProxyInstance代码中需要interfaces参数&am…