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