RelativePositionedTransition
用于基于相对位置的动画效果
RelativePositionedTransition是Flutter中的一个动画过渡组件,专门用于基于相对位置(相对于父容器)的动画效果。它通过监听一个动画对象(如Animation<Rect>),动态调整子组件的位置和大小,实现平滑的过渡
动画。
核心逻辑: 在动画过程中,组件根据动画值计算子组件的相对矩形区域(位置和尺寸),并实时更新UI。
主要用途:
- 实现子组件在父容器内的位置和大小变化动画(如缩放、移动)。
- 适用于需要精确控制子组件相对父容器边界的动画场景(如弹窗展开、图标拖拽)。
使用场景:
- 弹窗动画: 例如,一个对话框从屏幕中心放大出现。
- 交互反馈: 按钮被点击时,图标位置平滑移动到新位置。
- 布局切换: 在网格布局和列表布局之间切换时,元素的位置过渡。
示例
基础位置过渡
import 'package:flutter/material.dart';
class BasicTransitionExample extends StatefulWidget {
_BasicTransitionExampleState createState() => _BasicTransitionExampleState();
}
class _BasicTransitionExampleState extends State<BasicTransitionExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Rect> _animation;
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
)..repeat(reverse: true); // 循环播放动画
// 定义相对位置动画:从左上角(10,10)到右下角(200,200),大小从50x50变为100x100
_animation = RectTween(
begin: Rect.fromLTWH(10, 10, 50, 50), // 初始位置和大小
end: Rect.fromLTWH(200, 200, 100, 100), // 结束位置和大小
).animate(_controller);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('基础位置过渡')),
body: Container(
width: 300,
height: 300,
color: Colors.grey[200],
child: RelativePositionedTransition(
rect: _animation,
size: Size(300, 300), // 父容器大小
child: Container(color: Colors.blue), // 动画子组件
),
),
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
交互式动画
import 'package:flutter/material.dart';
class InteractiveTransitionExample extends StatefulWidget {
_InteractiveTransitionExampleState createState() => _InteractiveTransitionExampleState();
}
class _InteractiveTransitionExampleState extends State<InteractiveTransitionExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Rect> _animation;
bool _isMoved = false;
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
// 初始化动画:从左侧移动到右侧
_updateAnimation();
}
void _updateAnimation() {
_animation = RectTween(
begin: _isMoved ? Rect.fromLTWH(200, 50, 80, 80) : Rect.fromLTWH(20, 50, 80, 80),
end: _isMoved ? Rect.fromLTWH(20, 50, 80, 80) : Rect.fromLTWH(200, 50, 80, 80),
).animate(CurvedAnimation(parent: _controller, curve: Curves.easeInOut));
}
void _togglePosition() {
setState(() {
_isMoved = !_isMoved;
_updateAnimation();
_controller.forward(from: 0); // 重新播放动画
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('交互式动画')),
body: Center(
child: Column(
children: [
Container(
width: 300,
height: 200,
color: Colors.grey[200],
child: RelativePositionedTransition(
rect: _animation,
size: Size(300, 200),
child: Container(color: Colors.green),
),
),
ElevatedButton(
onPressed: _togglePosition,
child: Text(_isMoved ? '移回左侧' : '移到右侧'),
),
],
),
),
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
主题适配动画
import 'package:flutter/material.dart';
class ThemedTransitionExample extends StatefulWidget {
_ThemedTransitionExampleState createState() => _ThemedTransitionExampleState();
}
class _ThemedTransitionExampleState extends State<ThemedTransitionExample>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Rect> _positionAnimation;
late Animation<Color?> _colorAnimation;
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 3),
vsync: this,
)..repeat(reverse: true);
// 位置动画:从顶部移动到底部
_positionAnimation = RectTween(
begin: Rect.fromLTWH(50, 10, 100, 50),
end: Rect.fromLTWH(50, 150, 100, 50),
).animate(_controller);
// 颜色动画:从蓝色过渡到红色
_colorAnimation = ColorTween(
begin: Colors.blue,
end: Colors.red,
).animate(_controller);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('主题适配动画')),
body: AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
width: 200,
height: 200,
color: Colors.grey[200],
child: RelativePositionedTransition(
rect: _positionAnimation,
size: Size(200, 200),
child: Container(color: _colorAnimation.value), // 动态颜色
),
);
},
),
);
}
void dispose() {
_controller.dispose();
super.dispose();
}
}
注意点
常见问题与解决方案:
-
性能瓶颈:
- 问题: 频繁更新动画可能导致UI卡顿(尤其在低端设备上)。
- 解决: 使用
CurvedAnimation优化动画曲线(如Curves.easeInOut),避免复杂计算。对于连续动画,确保AnimationController在页面销毁时被释放(dispose())。
-
兼容性警告:
- 问题: 父容器大小变化时,动画可能错位。
- 解决: 始终通过
size参数指定父容器的精确尺寸,避免依赖动态布局。推荐在LayoutBuilder中获取父容器大小。
优化技巧:
- 重用动画控制器: 多个
RelativePositionedTransition组件可共享同一个AnimationController以减少资源开销。 - 预计算动画值: 对于复杂路径,使用
RectTween提前定义起始/结束状态,避免在构建过程中计算。
最佳实践:
- 将动画逻辑封装在
StatefulWidget中,利用SingleTickerProviderStateMixin简化动画管理。 - 测试动画在不同屏幕尺寸下的表现,确保
Rect值适配响应式布局。
构造函数
RelativePositionedTransition({
Key? key,
required Animation<Rect> rect, // 必需参数:控制位置和尺寸的动画对象
required Size size, // 必需参数:父容器的尺寸
Widget? child, // 可选参数:被动画控制的子组件
})
属性
| 属性名 | 属性类型 | 说明 |
|---|---|---|
rect | Animation<Rect> | 必需。定义子组件相对父容器的位置和尺寸动画,通过Rect对象(包含left、top、width、height)控制变化。 |
size | Size | 必需。指定父容器的大小(宽度和高度),用于正确计算相对位置。 |
child | Widget | 可选。被动画控制的子组件,如果为null,则动画不渲染任何内容。 |
关键属性详解:
rect: 这是组件的核心属性,接受一个Animation<Rect>对象。Rect表示一个矩形区域,其值通过动画插值计算而来(例如从Rect.fromLTWH(0,0,50,50)到Rect.fromLTWH(100,100,100,100))。任何变化都会触发子组件重新布局,性能敏感,建议使用RectTween优化插值计算。size: 必须与父容器的实际尺寸严格匹配,否则动画位置会偏移。在响应式布局中,可通过LayoutBuilder动态获取父容器大小,例如:
LayoutBuilder(
builder: (context, constraints) {
return RelativePositionedTransition(
size: Size(constraints.maxWidth, constraints.maxHeight),
// ... 其他参数
);
},
)