由于业务需要,在打开列表时,列表项需要一个从右边飞入的动画效果,故封装一个专门可以执行动画的列表组件,可以自定义自己的动画,内置有水平滑动,缩放等简单动画。花里胡哨的动画效果由你自己来定制吧。
功能:
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,});@overrideState<StatefulWidget> createState() => _KqAnimateListViewState<T>();
}class _KqAnimateListViewState<T> extends State<KqAnimateListView<T>>with TickerProviderStateMixin {late IAnimate animate;late AnimationController controller;late Animation animation;@overridevoid initState() {super.initState();animate = widget.animate ?? ScaleAnimate();animate.init(widget.data.length);controller = animate.getAnimationController(this);animation = animate.getAnimation(controller, this);//启动动画(正向执行)controller.forward();}@overridevoid 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();}}@overrideWidget 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;}@overridevoid dispose() {controller.dispose();super.dispose();}
}///动画抽象类。
///实现该类,定制自己的列表动画。
abstract class IAnimate {///初始化///[length] 列表长度void init(int length);///获取AnimationControllerAnimationController getAnimationController(TickerProvider provider);///获取AnimationAnimation getAnimation(AnimationController controller, State<StatefulWidget> state);///定制自己的动画,每一个item都会调用到animate,///当需要差异动画时需要根据[index]计算不同item的动画时机///[widget] 执行动画之后的widget///[index] 列表的item的indexWidget 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});@overridevoid init(int length) {gap = length * twoGap + baseGap;}@overrideWidget animate(BuildContext context, int index, Widget widget,Animation animation, AnimationController controller) {double width = context.width;///范围0->1double 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,);}@overrideAnimationController getAnimationController(TickerProvider provider) {return AnimationController(duration: Duration(milliseconds: (gap * rate).toInt()),vsync: provider);}@overrideAnimation 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;@overrideWidget animate(BuildContext context, int index, Widget widget,Animation animation, AnimationController controller) {double width = context.width;///范围0.5->1double 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,);}@overrideAnimationController getAnimationController(TickerProvider provider) {return AnimationController(duration: Duration(milliseconds: (gap * rate).toInt()),vsync: provider);}@overrideAnimation getAnimation(AnimationController controller, State<StatefulWidget> state) {return Tween(begin: 0.0, end: gap).animate(controller)..addListener(() {if (state.mounted) {state.setState(() {});}});}@overridevoid 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,)