Flutter实现动画列表AnimateListView

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

功能:

1.可自定义动画。

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

演示:

代码:

Dart 复制代码
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;
  }
}

使用:

Dart 复制代码
                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,
                  )
相关推荐
孤鸿玉5 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥12 小时前
Flutter Riverpod上手指南
android·flutter·ios
BG1 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng1 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭1 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯1 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan1 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓1 天前
Flutter Getx 的页面传参
flutter
火柴就是我2 天前
flutter 之真手势冲突处理
android·flutter
Speed1232 天前
`mockito` 的核心“打桩”规则
flutter·dart