如何正确使用曲线的值来为小部件设置动画?
How to properly use curve's value to animate a widget?
最小可重现代码:
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
的值以匹配默认行为。
假设:每毫秒周期性调用一次回调。
预期结果: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
。引用 docs
If a periodic timer with a non-zero duration is delayed too much, so more than one tick should have happened, all but the last tick in the past are considered "missed", and no callback is invoked for them. The tick count reflects the number of durations that have passed and not the number of callback invocations that have happened.
因此,tick
告诉我们已经通过的 period
的数量。下面的代码将赶上 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
使用 [=21] =].区别在于 Timer.periodic
是由我们作为句点传递的 Duration
驱动的,而 Ticker
是由 SchedulerBinding.scheduleFrameCallback
驱动的。因此,值 _dx2
更新的瞬间和帧在屏幕上呈现的瞬间可能不同。添加一些回调被遗漏的事实!
最小可重现代码:
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
的值以匹配默认行为。
假设:每毫秒周期性调用一次回调。
预期结果: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
。引用 docs
If a periodic timer with a non-zero duration is delayed too much, so more than one tick should have happened, all but the last tick in the past are considered "missed", and no callback is invoked for them. The tick count reflects the number of durations that have passed and not the number of callback invocations that have happened.
因此,tick
告诉我们已经通过的 period
的数量。下面的代码将赶上 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
使用 [=21] =].区别在于 Timer.periodic
是由我们作为句点传递的 Duration
驱动的,而 Ticker
是由 SchedulerBinding.scheduleFrameCallback
驱动的。因此,值 _dx2
更新的瞬间和帧在屏幕上呈现的瞬间可能不同。添加一些回调被遗漏的事实!