- 从零开始用flutter写一个完整应用(14):动画3--物理模
- 从零开始用flutter写一个完整应用⑺:页面过渡动画
- 从零开始用flutter写一个完整应用(11):持久化储存3--
- 从零开始用flutter写一个完整应用⑵:Lottie启动动画
- 从零开始用flutter写一个完整应用⑴:Android stu
- 从零开始用flutter写一个完整应用⑸:网络请求
- 从零开始用flutter写一个完整应用⑻:Gestures 手势
- 从零开始用flutter写一个完整应用(12):动画1--淡入淡
- 从零开始用flutter写一个完整应用(13):动画2--渐变效
- 从零开始用flutter写一个完整应用⑶:底部导航栏
说明
接上篇,这篇说说物理模拟动画效果。它是这几种动画最复杂的一种,也是能实现效果最丰富的一种,物理模拟能够让应用富有真实感和更好的交互性。例如,你可能会为一个 widget 添加动画,让它看起来就像安着弹簧,或是在随重力下落。下面以如何将 widget 从拖动的点移回到中心,并使用弹簧模拟效果,为示例演示它的使用。
创建一个动画控制器
让
_DraggableCardState
类继承至 SingleTickerProviderStateMixin。然后在initState
中构造一个 AnimationController,并将其vsync
属性设为this
。继承的SingleTickerProviderStateMixin
让 state 对象为AnimationController
提供了TickerProvider
的能力。要获得更多信息,请查看 TickerProvider 文档
class _DraggableCardState extends State<DraggableCard> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 1));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
使用手势移动 widget
让 widget 可以被拖拽,并为
_DraggableCardState
类添加一个 Alignment 范围。添加一个 GestureDetector 来捕获onPanDown
、onPanUpdate
,以及onPanEnd
回调。为了调整对齐方式,请使用 MediaQuery 来获得 widget 的大小,然后除以 2。(这会将「拖动的像素」单位转为 Align使用的坐标。)然后,将Align
widget 的alignmnt
属性设为_dragAlignment
。
Alignment _dragAlignment = Alignment.center;
@override
Widget build(BuildContext context) {
return GestureDetector(
onPanDown: (details) {},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
对 widget 进行动画,计算速度以模拟弹跳运动
当一个 widget 被释放,它应该就会弹回中心。
添加一个 Animation<Alignment>,以及 _runAnimation 方法。此方法定义了一个 Tween,它在 widget 被拖动到的点之间插入到中心点。引入 physics 这个 package,用于计算速度
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
完整示例
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
void main() {
runApp(const MaterialApp(home: PhysicsCardDragDemo()));
}
class PhysicsCardDragDemo extends StatelessWidget {
const PhysicsCardDragDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: const DraggableCard(
child: FlutterLogo(
size: 128,
),
),
);
}
}
class DraggableCard extends StatefulWidget {
const DraggableCard({required this.child, super.key});
final Widget child;
@override
_DraggableCardState createState() => _DraggableCardState();
}
class _DraggableCardState extends State<DraggableCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
Alignment _dragAlignment = Alignment.center;
late Animation<Alignment> _animation;
void _runAnimation(Offset pixelsPerSecond, Size size) {
_animation = _controller.drive(
AlignmentTween(
begin: _dragAlignment,
end: Alignment.center,
),
);
final unitsPerSecondX = pixelsPerSecond.dx / size.width;
final unitsPerSecondY = pixelsPerSecond.dy / size.height;
final unitsPerSecond = Offset(unitsPerSecondX, unitsPerSecondY);
final unitVelocity = unitsPerSecond.distance;
const spring = SpringDescription(
mass: 30,
stiffness: 1,
damping: 1,
);
final simulation = SpringSimulation(spring, 0, 1, -unitVelocity);
_controller.animateWith(simulation);
}
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
_controller.addListener(() {
setState(() {
_dragAlignment = _animation.value;
});
});
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return GestureDetector(
onPanDown: (details) {
_controller.stop();
},
onPanUpdate: (details) {
setState(() {
_dragAlignment += Alignment(
details.delta.dx / (size.width / 2),
details.delta.dy / (size.height / 2),
);
});
},
onPanEnd: (details) {
_runAnimation(details.velocity.pixelsPerSecond, size);
},
child: Align(
alignment: _dragAlignment,
child: Card(
child: widget.child,
),
),
);
}
}
网友评论