-
滑动曲线优化,拉齐手感 -
渲染性能优化,减少掉帧
滑动对比
我们先使用platform_tests对比一下Flutter的滑动和Native的差异
(视频的左侧列表是Native,右侧列表是Flutter)
Android
iOS
从视频的表现中,我们可以看出来iOS的脱手滑动略有差异但还是基本符合预期的,Android的脱手滑动就比较糟糕了看起来明显阻尼比Native大很多。
Android滑动曲线阻尼问题
ScrollPhysics.createBallisticSimulation()
构建一个Simulation,这个Simulation(ClampingScrollSimulation/BouncingScrollSimulation)就是最终控制脱手滑动动画参数的类。问题分析
在ClampingScrollSimulation构造函数中,会根据传入的初速度,并结合一些小学二年级就学过的运动学公式计算出最终的距离以及滑动的总时间(因为没找到确定性的资料,只能根据参数推测,这边先不展开讨论)
计算出总时长和总距离后,就可以根据当前时长的比例(动画运行的时间/总时长),带入公式计算出当前应该位移的距离,最后再加上初始位置即可得到最终Viewport的偏移量。
这个公式是对Android的滑动曲线进行拟合产生的,拟合过程可以看注释,但是这个拟合并不完美。我们对这个d/t函数进行求导(即速度和时间的关系)可以发现,它的导数在0-1的区间内并不是单调递减的,这意味着在滑动接近结束的时候会有一个速度增加的情况,体感上在Clamping曲线滑动动画的末尾感觉上会有一个轻微的吸附感也说明了这一点。所以我们需要把Clamping的实现改一下,把Android的Scroller中计算滑动距离的代码弄过来。
double x(double time)
方法中把时间和position打出来,发现了一个问题,time一直在清零,position也一直在变。这说明了这个动画一直在被重新启动,我们简单改一下代码,强行让动画不要重启,使用同样的ADB指令进行滑动,通过Log对比可以发现这确实会导致滑动速度更快地衰减。-
解决发生Layout时的动画重启问题 -
Clamping的实现方式对齐Android的Scroller
问题解决
-
动画重启问题
我们在ClampingScrollSimulation
的构造函数中断点,可以发现当Fling过程中如果Item高度发生改变,则会触发RenderViewport.performLayout()
,从而触发ScrollPositionWithSingleContext.goBallistic()
,这个方法会以当前的状态为起点重新启动fling动画,这显然是不合理的,特别是当前滑动和边界无关的时候没必要因为布局改变而重启动画,这会导致一次Fling动画无法完整做完,移动的轨迹自然也就无法贴合预期中设计好的d/t曲线。
这个问题比较明确,所以解决问题的思路也很明确,就是在Fling的过程中不要重启动画了,而是去更新一些相关的变量,使得动画能够合理的继续完成。一开始我是希望更新Simulation中和边界有关的的相关参数,但是因为这个方案有个始终绕不过去的类型检查(https://github.com/flutter/flutter/pull/96512)Flutter团队的同学认为不能通过。所以诞生了下面的新方案(https://github.com/flutter/flutter/pull/100133)
首先解决更新时机的问题,当RenderViewport.performLayout()
被调用的时候,会回调ScrollActivity.applyNewDimensions
,在惯性滑动的过程中的ScrollActivity是BallisticScrollActivity
ScrollPositionWithSingleContext.goBallistic()
也就是在这个方法中被调用,所以我们在这边做一下修改,让其不再调用goBallistic()
,而是调用updateBallisticAnimation()
生成一个新的Simulation,并将其更新到AnimationController中。updateBallisticAnimation()
中,我们还是使用createBallisticSimulation()
来创建Simulation。这里重要的一点是,因为我们动画时间没有清零,所以我们创建Simulation的时候一定要用初始的ScrollMetrics和Velocity来创建。因为布局变化有可能会带来相应的边界变化,所以这里只将相应的ScrollExtent(也就是边界值)更新为最新的值即可。Android滑动曲线对齐
滑动速度引发的问题
如何合理的给曲线减速
BouncingScrollPhysics
创建BouncingScrollSimulation
时,给初速度乘了0.91。我们最初使用的曲线也是这个减速版本的,所以切换到正常的曲线后才会显得比较快。这个0.91在一次PR中被删除了(https://github.com/flutter/flutter/pull/59623)原因是导致了iOS曲线阻尼过大,但是实际上背后的原因,其实是上面提到的动画重启问题,因为动画重启导致速度多次乘了0.91,才使得滑动速度加速衰减。那么现在问题解决了,我们是不是也可以尝试通过这种方式对曲线进行减速。iOS的滑动公式比较简单,我们就通过iOS来分析,Android也是同样的道理。如何让滑动能快能慢
y=ax^2+bx+c
,中间低两边高恰好满足我的诉求,当用户滑动得很慢或很快的时候不增加衰减,当用户在中速滑动的时候给出最大衰减。为了方便在线上做实验以及数据分析,我们尽量把参数缩减到一个,我选择了对称的图形(强迫症),所以必过两个点(0,1)和(1,1),并且顶点的x坐标也确定为了0.5,剩下一个顶点y坐标,初中学的顶点式就不多说了吧,直接上代码。最终效果
最终滑动的效果符合我们的预期,上线进行了分桶实验,我们得出了一个最佳实践的衰减顶点值在0.7左右,效果大概如视频所示。调整曲线的按钮是临时增加的,切换不同曲线并且使用同一个adb命令进行滑动,可以看出来使用新的曲线在快速滑动的情况下会比之前的曲线多滑动一到两屏。
总结和展望
-
修复动画重启问题:https://github.com/flutter/flutter/pull/100133 -
Android曲线复刻:https://github.com/flutter/flutter/pull/77497
-
推进本文提到的几个PR的合入 -
结合业务数据并配合交互设计师,对滑动手感进行更精细的控制 -
在滑动的过程中根据速度的不同,结合图片库进行图片加载控制,以提升滑动流畅度
文章评论