Flutter提供了四个Widget可实现平移动画,分别是
SlideTransition 是基于 Animation
例:Widget 的width=100,height = 200,平移动画的起点为Offset(0,0),终点 为Offset(2,3),则平移动画移动的宽度就是 width*dx = 100 *2 ;高度就是 height * dy= 200 *3
大概步骤:
具体代码:
class SlideTransitionPage extends StatefulWidget {
const SlideTransitionPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _SlideTransitionPageState();
}
class _SlideTransitionPageState extends State<SlideTransitionPage>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
/// 重复播放,持续时间为5秒的动画控制器
_controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
/// 需要平移的Widget 宽高
const childHeight = 100.0;
const childWidth = 150.0;
/// 父布局的宽高
final parentWidth = constraints.maxWidth;
final parentHeight = constraints.maxHeight;
/// 动画起点是左上角
const startOffset = Offset(0, 0);
/// 动画终点右下角(父布局宽高 - 子控件宽高)/子控件宽高
/// 因为平移时子控件大小会占用一部分空间所以需要减去
final dx = (parentWidth - childWidth) / childWidth;
final dy = (parentHeight - childHeight) / childHeight;
final endOffset = Offset(dx, dy);
/// 补间动画
final offsetAnimation =
Tween<Offset>(begin: startOffset, end: endOffset)
.animate(_controller);
return SlideTransition(
position: offsetAnimation,
child: Container(
width: childWidth,
height: childHeight,
color: Colors.red,
),
);
},
),
);
}
}
如果只是实现 topLeft 、topCenter 、topRight 、centerLeft、center 、centerRight 、bottomLeft、bottomCenter 、bottomRight 这些特殊点位的平移,使用AlignTransition则比较方便。
AlignTransition 是基于 Alignment 来确定平移位置的。
非线性动画需要使用CurvedAnimation
class AlignTransitionPage extends StatefulWidget {
const AlignTransitionPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _AlignTransitionPageState();
}
class _AlignTransitionPageState extends State<AlignTransitionPage>
with SingleTickerProviderStateMixin {
/// 可重复播放 持续时间为5秒
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)
..repeat(reverse: true);
/// 左下角 到 右上角
late final Animation<AlignmentGeometry> _animation = Tween<AlignmentGeometry>(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
).animate(
/// 非线性动画,可查看Curves源码,看具体的效果
CurvedAnimation(
parent: _controller,
curve: Curves.easeInCirc,
),
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: AlignTransition(
alignment: _animation,
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
);
}
}
PositionedTransition 是基于 RelativeRect 来确定平移位置的。从rect1 平移至 rect2,若Rect大小不一致,则可改变Widget尺寸大小。
PositionedTransition 必须放置在
Stack
Widget 中。
RelativeRect:相对于父容器尺寸来确定自身的位置、大小。
class PositionedTransitionPage extends StatefulWidget {
const PositionedTransitionPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _PositionedTransitionPageState();
}
class _PositionedTransitionPageState extends State<PositionedTransitionPage>
with SingleTickerProviderStateMixin {
/// 可重复播放,持续时间为5秒的动画控制器
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat(reverse: true);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
/// 计算容器的尺寸
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final Size biggest = constraints.biggest;
/// 需要平移的Widget 宽高
const childHeight = 40.0;
const childWidth = 60.0;
/// 平移后Widget 变成3倍大小
const targetChildHeight = childHeight * 3;
const targetChildWidth = childWidth * 3;
/// 计算起始位置 左上角 -> 右下角
/// 根据自身大小,以及父布局大小获取相对位置
var beginRect = RelativeRect.fromSize(
const Rect.fromLTWH(0, 0, childWidth, childHeight),
biggest,
);
var endRect = RelativeRect.fromSize(
Rect.fromLTWH(
biggest.width - targetChildWidth,
biggest.height - targetChildHeight,
targetChildWidth,
targetChildHeight,
),
biggest,
);
/// 补间动画
final rectAnimation =
RelativeRectTween(begin: beginRect, end: endRect)
.animate(_controller);
return Stack(
children: [
PositionedTransition(
rect: rectAnimation,
child: Container(
width: childWidth,
height: childHeight,
color: Colors.red,
),
),
],
);
},
),
);
}
}
RelativePositionedTransition 与 PositionedTransition类似,只是RelativePositionedTransition需要传参父容器的尺寸以及平移起始位置,它帮我们计算RelativeRect。
RelativePositionedTransition 必须放置在
Stack
Widget 中。
RelativePositionedTransition 基于 Rect 与 Size 确定平移位置
class RelativePositionedTransitionPage extends StatefulWidget {
const RelativePositionedTransitionPage({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _RelativePositionedTransitionPageState();
}
class _RelativePositionedTransitionPageState extends State<RelativePositionedTransitionPage>
with SingleTickerProviderStateMixin {
/// 可重复播放,持续时间为5秒的动画控制器
late final AnimationController _controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)..repeat(reverse: true);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
/// 计算容器的尺寸
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
final Size biggest = constraints.biggest;
/// 需要平移的Widget 宽高
const childHeight = 40.0;
const childWidth = 60.0;
/// 平移后Widget 变成3倍大小
const targetChildHeight = childHeight * 3;
const targetChildWidth = childWidth * 3;
/// 计算起始位置 左上角 -> 右下角
var beginRect = const Rect.fromLTWH(0, 0, childWidth, childHeight);
var endRect = Rect.fromLTWH(
biggest.width - targetChildWidth,
biggest.height - targetChildHeight,
targetChildWidth,
targetChildHeight,
);
/// 补间动画
final rectAnimation =
RectTween(begin: beginRect, end: endRect).animate(
CurvedAnimation(
parent: _controller,
curve: Curves.easeInCirc,
),
);
return Stack(
children: [
RelativePositionedTransition(
rect: rectAnimation,
size: biggest,
child: Container(
width: childWidth,
height: childHeight,
color: Colors.red,
),
),
],
);
},
),
);
}
}