Flutter物理动画的浅析

Flutter物理动画的资料实在太少了,等不及大佬们的博客,我就自己研究源码了。

在我们平时写的ListView和PageView中,所给定的ClampingScrollPhysics与BouncingScrollPhysics等都有这一系列的物理动画。

ListView惯性滑动的运动模型

一个木板在不光滑的水平面上,给它一个初速度,它会受滑动摩擦力逐渐停止,Flutter中ClampingScrollPhysics这个physics更近似的模拟了这个动画,而BouncingScrollPhysics其实并不近似于这个动画,抛开ListView达到顶部的动画差异,我们用力滑动BouncingScrollPhysics的ListView,就会发现,不管给它多大的初速度,它的最大滑动速度远不及ClampingScrollPhysics,在较长的列表中可以表现出来。

从物理的角度自定义简单的动画

这部分应该完全来自高中物理,典型的汽车刹车运动模型。

  • 这块木板的名字是ListView

  • 假设屏幕无限长,且不光滑,存在一定系数的动摩擦因数,ListView有它的宽度,我们把它画厚一点。

  • 设手指离开屏幕时,ListView向右滑动

受力分析--->

所以它在接下来的运动中只受到来自屏幕给它的摩擦力,方向水平向左。

由动力学公式

f = μN

N = mg

F = m*a

f=μN=μmg=ma

即可得出这个ListView的加速度。所以在水平运动时加速度与物体的质量是无关的。

滑动摩擦力存在的大前提是物体相对有压力

物体在滑动中滑动摩擦力为恒力,此时物体水平合力F即为f,在设备中,μ是动摩擦系数,自然是模拟的一个固定数值,所以整个滑动摩擦力f与加速度都是模拟的。

有以下两个公式

公式中的初末为下标

1.v末=v初+at

2.x=v初*t+0.5*a*t*t

由于ListView末速度为0,所以第一个公式可得时间t,代入第二个公式可得它运动的位移x。

一般这种情况,我们往往用一个快捷的公式

3.v末*v末-v初*v初=2ax

所以通过一个公式算出时间,另外两个之一可得出位移。

有了物体还需要运动的时间,跟运动的位移,动画就好写了。再通过公式2算出动画执行时的每个瞬间物体应该位移到的距离。

代码的简单实现

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设置为禁止滑动

physics: NeverScrollableScrollPhysics(),

使用GestureDetector接收滑动事件

简单计算出手指从按下的点到滑动的点产生的位移并及时控制ListView的位置

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,

),

),

预览

可以看到它现在没有任何的惯性

创建ClampingScrollPhysics效果的惯性动画

构建Simulation对象

ClampingScrollSimulation对象需要三个参数:

  • position:运动的起始位移
  • velocity:手指离开屏幕每秒的偏移量
  • tolerance:一个公差,属性有运动的距离,时间,每一时刻的速度,这个玩意我的理解不深刻,由于我想要与原ListView的惯性效果一样(不考虑到达边界),所以我直接copy的官方源码。

创建动画控制器

这儿也有一个需要注意的点

需要用

AnimationController(

vsync: this,

value: 0,

lowerBound: double.negativeInfinity,

upperBound: double.infinity,

);

来实例化这个控制器,不然它的默认上下界为0~1

监听动画控制器

animationController.addListener(() {

scrollController.jumpTo(animationController.value);

});

通过Simulation来执行动画

animationController.animateWith(simulation);

这一节的完整代码,需要放在手指离开屏幕的回调中.

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的弹簧效果

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;

} elseif (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的源码。

结语

  • 有的时候还是需要自己揣摩一下原理,自己多动手,不能一直组别人的轮子
  • 数学跟物理在动画中始终有着很大的作用
  • 高考物理差班上第一名两分始终记着🤣

下一篇是利用本章知识解决各种复杂的滑动联动问题(应该吧🤪 )。

以上是 Flutter物理动画的浅析 的全部内容, 来源链接: utcz.com/a/24254.html

回到顶部