flutter - 如何正确使用曲线的值来为小部件设置动画?
问题描述
最小的可重现代码:
class _MyPageState extends State<MyPage> {
double _dx1 = 0;
double _dx2 = 0;
final Duration _duration = Duration(seconds: 1);
final Curve _curve = Curves.linear;
void _play() {
final width = MediaQuery.of(context).size.width;
_dx1 = width;
var i = 0;
Timer.periodic(Duration(milliseconds: 1), (timer) {
if (i > 1000) {
timer.cancel();
} else {
setState(() {
_dx2 = _curve.transform(i / 1000) * width;
i++;
});
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: _play,
child: Icon(Icons.play_arrow),
),
body: SizedBox.expand(
child: Stack(
children: [
AnimatedPositioned(
left: _dx1,
duration: _duration,
curve: _curve,
child: _box,
),
Positioned(
left: _dx2,
top: 60,
child: _box,
),
],
),
),
);
}
Container get _box => Container(width: 50, height: 50, color: Colors.red);
}
输出:
如您所见,我的第二个自定义动画框没有赶上第一个默认AnimatedPositioned
小部件。
注意:这可以很容易地使用Tween
,AnimationController
但我只想知道为什么我无法正确使用Curve
's 的值来匹配默认行为。
解决方案
假设:每毫秒定期调用回调。
预期结果:1秒后,i = 1000;
假设是错误的。添加以下代码进行验证:
void _play() {
...
var i = 0;
final start = DateTime.now().millisecondsSinceEpoch;
Timer.periodic(Duration(milliseconds: 1), (timer) {
final elapse = DateTime.now().millisecondsSinceEpoch - start;
print('elapse = $elapse');
print('i = $i');
print('ticks = ${timer.tick}');
print('######################');
...
});
}
在我的电脑上,最后一个值为:
elapse = 14670
i = 1001
ticks = 14670
######################
因此,这意味着在我的 PC 上进行 1001 次回调需要 14 秒。这不是我们所期待的。从中我们可以推断出,有些回调被遗漏了,i
并没有反映经过的时间。
但是,我们需要的值是timer.tick
。引用文档
如果具有非零持续时间的周期性计时器延迟太多,那么应该发生不止一个滴答,除了过去的最后一个滴答外,其他所有滴答都被视为“错过”,并且不会为它们调用回调。滴答计数反映了已经过去的持续时间的数量,而不是已经发生的回调调用的数量。
所以,tick
告诉我们period
已经通过的 s 的数量。下面的代码将赶上AnimatedPositioned
void _play() {
final width = MediaQuery.of(context).size.width;
_dx1 = width;
final period = Duration(milliseconds: 1);
Timer.periodic(period, (timer) {
final elapsed = timer.tick * period.inMilliseconds;
if (elapsed > _duration.inMilliseconds) {
timer.cancel();
} else {
setState(() {
_dx2 = _curve.transform(elapsed / _duration.inMilliseconds) * width;
});
}
});
}
现在,您可能会看到一些口吃,我们的盒子可能有点领先或落后于AnimatedPositioned
,这是因为我们使用Timer
但AnimatedPositioned
使用Ticker
. 差异是Timer.periodic
由Duration
我们作为周期传递的驱动,但Ticker
由 驱动SchedulerBinding.scheduleFrameCallback
。因此,值_dx2
更新的瞬间和帧在屏幕上呈现的瞬间可能不一样。添加错过了一些回调的事实!
推荐阅读
- html - Bootstrap 年数自动递增
- spring-boot - SpringBoot-JPA:“传递给持久化的分离实体”-OneToMany 双向
- git - 我可以在 git 中使用基于 ID 的 GitHub 提供的 noreply 地址吗?
- shell - 如何在 shell 脚本上使用 gnuplot 命令
- javascript - C3.js 水平条形图 - 与条形一起突出显示特定的 x 轴刻度
- tensorflow - 无法将 tensorflow.python.data.ops.dataset_ops.PrefetchDataset 类型的参数解释为迭代过程中的 TFF 值
- excel - 如何在excel中将月份和日期添加到编号的年份?
- azure - 用于部署 Net Core 3.1 webapp 项目和 Azure Function App 项目的管道
- android - Jetpack Compose 找到父母的宽度/长度
- python - Python pip 在安装 django-braces 时引发 NewConnectionError