Flutter 物理动画的资料实在太少了,等不及大佬们的博客,我就自己研究源码了。
在我们平时写的 ListView 和 PageView 中,所给定的 ClampingScrollPhysics 与 BouncingScrollPhysics 等都有这一系列的物理动画。
ListView 惯性滑动的运动模型
一个木板在不光滑的水平面上,给它一个初速度,它会受滑动摩擦力逐渐停止,Flutter 中 ClampingScrollPhysics 这个 physics 更近似的模拟了这个动画,而 BouncingScrollPhysics 其实并不近似于这个动画,抛开 ListView 达到顶部的动画差异,我们用力滑动 BouncingScrollPhysics 的 ListView,就会发现,不管给它多大的初速度,它的最大滑动速度远不及 ClampingScrollPhysics,在较长的列表中可以表现出来。
从物理的角度自定义简单的动画
这部分应该完全来自高中物理,典型的汽车刹车运动模型。
受力分析—>
所以它在接下来的运动中只受到来自屏幕给它的摩擦力,方向水平向左。
由动力学公式
f=μN=μmg=ma
即可得出这个 ListView 的加速度。所以在水平运动时加速度与物体的质量是无关的。
滑动摩擦力存在的大前提是物体相对有压力
物体在滑动中滑动摩擦力为恒力,此时物体水平合力 F 即为 f,在设备中,μ 是动摩擦系数,自然是模拟的一个固定数值,所以整个滑动摩擦力 f 与加速度都是模拟的。
有以下两个公式
公式中的初末为下标
1 2
| 1.v末=v初+at 2.x=v初*t+0.5*a*t*t
|
由于 ListView 末速度为 0,所以第一个公式可得时间 t,代入第二个公式可得它运动的位移 x。
一般这种情况,我们往往用一个快捷的公式
所以通过一个公式算出时间,另外两个之一可得出位移。
有了物体还需要运动的时间,跟运动的位移,动画就好写了。再通过公式 2 算出动画执行时的每个瞬间物体应该位移到的距离。
代码的简单实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| class SimulationWidget extends StatefulWidget { @override _SimulationWidgetState createState() => _SimulationWidgetState(); }
class _SimulationWidgetState extends State<SimulationWidget> with SingleTickerProviderStateMixin { AnimationController animationController; double friction = 9; //动摩擦因素 double distance = 0.0; //运动位移 double startVelocity = 200; //初速度 double time; //运动所需时间 double acceleration; //加速度 double g=9.8; @override void initState() { super.initState(); acceleration = friction * g; time = -startVelocity / -acceleration; animationController = AnimationController( vsync: this, duration: Duration(milliseconds: time.toInt() * 1000)); animationController.addListener(() { double tmpTime = time * animationController.value; distance = startVelocity * tmpTime - 0.5 * acceleration * tmpTime * tmpTime; setState(() {}); }); }
@override Widget build(BuildContext context) { return Scaffold( floatingActionButton: FloatingActionButton( onPressed: () { animationController.reset(); animationController.forward(); }, ), body: Center( child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Padding( padding: EdgeInsets.only(left: distance), child: Container( width: 20, height: 20, color: Colors.green, ), ) ], ), ), ); } }
|
效果如下
创建 Flutter 自定义的物理动画
不使用任何的 physics,关于 physics 中的 Simulation 可以参考猫大的掘金
Flutter 完整开发实战详解(十八、 神奇的 ScrollPhysics 与 Simulation)
我们先写一个简单的 ListView
预览
将 physics 设置为禁止滑动
1
| physics: NeverScrollableScrollPhysics(),
|
使用 GestureDetector 接收滑动事件
简单计算出手指从按下的点到滑动的点产生的位移并及时控制 ListView 的位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| GestureDetector( onPanDown: (details){ preOffset=scrollController.offset; onPanDownOffset=details.globalPosition.dx; }, onHorizontalDragUpdate: (details){ scrollController.jumpTo(preOffset - (details.globalPosition.dx - onPanDownOffset)); }, child: Container( color: Colors.red, height: 200.0, width: MediaQuery.of(context).size.width, ), ),
|
预览
可以看到它现在没有任何的惯性
构建 Simulation 对象
ClampingScrollSimulation 对象需要三个参数:
- position:运动的起始位移
- velocity:手指离开屏幕每秒的偏移量
- tolerance:一个公差,属性有运动的距离,时间,每一时刻的速度,这个玩意我的理解不深刻,由于我想要与原 ListView 的惯性效果一样(不考虑到达边界),所以我直接 copy 的官方源码。
创建动画控制器
这儿也有一个需要注意的点
需要用
1 2 3 4 5 6
| AnimationController( vsync: this, value: 0, lowerBound: double.negativeInfinity, upperBound: double.infinity, );
|
来实例化这个控制器,不然它的默认上下界为 0~1
监听动画控制器
1 2 3
| animationController.addListener(() { scrollController.jumpTo(animationController.value); });
|
通过 Simulation 来执行动画
1
| animationController.animateWith(simulation);
|
这一节的完整代码,需要放在手指离开屏幕的回调中.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| final Tolerance tolerance = Tolerance( velocity: 1.0 / (0.050 * WidgetsBinding.instance.window .devicePixelRatio), // logical pixels per second distance: 1.0 / WidgetsBinding .instance.window.devicePixelRatio, // logical pixels ); double start = scrollController.offset; ClampingScrollSimulation clampingScrollSimulation = ClampingScrollSimulation( position: start, velocity: -details.velocity.pixelsPerSecond.dx, tolerance: tolerance, ); animationController = AnimationController( vsync: this, value: 0, lowerBound: double.negativeInfinity, upperBound: double.infinity, ); animationController.reset(); animationController.addListener(() { scrollController.jumpTo(animationController.value); }); animationController.animateWith(clampingScrollSimulation);
|
预览
可以看到,我们现在不借助 physics 就让 ListView 有了惯性滚动的效果
把 PageView 的弹性效果搬来是怎样呢?🧐
PageView 的弹簧效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| double velocity = -details.velocity.pixelsPerSecond.dx; double maxSize = 10000; double itemDimension = MediaQuery.of(context).size.width; double _getPixels(double page, double leading) { return (page * itemDimension) - leading; }
double _getPage(double pixels, double leading) { return (pixels + leading) / itemDimension; }
double _getTargetPixels( double pixels, Tolerance tolerance, double velocity, double leading, ) { double page = _getPage(pixels, leading);
if (pixels < 0) { return 0; }
if (pixels >= maxSize) { return maxSize; } if (pixels > 0) { if (velocity < -tolerance.velocity) { page -= 0.5; } else if (velocity > tolerance.velocity) { page += 0.5; } return _getPixels(page.roundToDouble(), leading); } } double start = scrollController.offset; double target = _getTargetPixels(start, tolerance, velocity, 0); var spring = SpringDescription.withDampingRatio( mass: 0.5, stiffness: 100.0, ratio: 1.1, ); ScrollSpringSimulation scrollSpringSimulation = ScrollSpringSimulation( spring, start, target, velocity, tolerance: tolerance); animationController = AnimationController( vsync: this, value: 0, lowerBound: double.negativeInfinity, upperBound: double.infinity, );
animationController.reset(); animationController.addListener(() { scrollController.jumpTo(animationController.value); }); animationController.animateWith(scrollSpringSimulation);
|
预览
这就实现了 PageView 中的弹簧动画
关于滑动到边界时候的动画处理还是需要参考 physics 的源码。
结语
- 有的时候还是需要自己揣摩一下原理,自己多动手,不能一直组别人的轮子
- 数学跟物理在动画中始终有着很大的作用
- 高考物理差班上第一名两分始终记着 🤣
下一篇是利用本章知识解决各种复杂的滑动联动问题(应该吧 🤪 )。