Flutter Ticker 动画驱动器

Posted 早起的年轻人

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter Ticker 动画驱动器相关的知识,希望对你有一定的参考价值。

题记:不到最后时刻,千万别轻言放弃,无论结局成功与否,只要你拼博过,尽力过,一切问心无愧。


Flutter文章目录


Flutter中的Ticker是一个动画驱动器,它会在每个屏幕刷新周期中调用回调函数,以便动画可以更新自己的状态并重绘。

Flutter Ticker 通常应用在需要在每一帧都执行一些操作的情况下,比如动画或者游戏开发中。

Ticker提供了一个每一帧都会回调的机制,可以用来更新UI或者执行其他操作。以下是一个使用Ticker的示例代码:

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

class MyWidget extends StatefulWidget 
  
  _MyWidgetState createState() => _MyWidgetState();


class _MyWidgetState extends State<MyWidget> with TickerProviderStateMixin 
  late final AnimationController _controller = AnimationController(
    duration: const Duration(seconds: 1),
    vsync: this,
  )..repeat();

  
  void dispose() 
    _controller.dispose();
    super.dispose();
  

  
  Widget build(BuildContext context) 
    return Center(
      child: RotationTransition(
        turns: Tween(begin: 0.0, end: 1.0).animate(_controller),
        child: const FlutterLogo(size: 200.0),
      ),
    );
  

上面的代码中,我们创建了一个MyWidget类,它继承自StatefulWidget。
在MyWidget的状态类_MyWidgetState中,我们混入了TickerProviderStateMixin,这个mixin提供了一个TickerProvider对象,用于创建AnimationController。

在_MyWidgetState的构造函数中,我们创建了一个AnimationController,它的duration为1秒,vsync为this,也就是_TickerProviderStateMixin的实例。然后我们调用了AnimationController的repeat方法,让动画重复播放。

在MyWidget的build方法中,我们创建了一个RotationTransition,它的turns属性是一个Tween,它的begin值为0,end值为1,表示从0度旋转到360度。

这个Tween的animate方法返回了一个Animation对象,它的值由AnimationController控制。最后我们把FlutterLogo放在RotationTransition中,就可以看到一个旋转的Flutter Logo了。

Ticker

Ticker使用了Flutter引擎的scheduler来调度回调函数的执行,scheduler会在每个屏幕刷新周期中调用回调函数。

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';

// Create a TickerProviderStateMixin
class MyWidget extends StatefulWidget 
  
  _MyWidgetState createState() => _MyWidgetState();


class _MyWidgetState extends State<MyWidget> with TickerProviderStateMixin 
  // Declare a Ticker object
  late Ticker _ticker;

  
  void initState() 
    super.initState();
    // Initialize the Ticker object
    _ticker = this.createTicker((Duration duration) 
      // Do something every tick
    );
  

  
  void dispose() 
    // Dispose the Ticker object
    _ticker.dispose();
    super.dispose();
  

  
  Widget build(BuildContext context) 
    return Container();
  


SingleTickerProviderStateMixin 是一个 mixin 类,它允许我们使用单个 TickerProvider 来管理多个 AnimationController。

import 'package:flutter/material.dart';

class MyWidget extends StatefulWidget 
  
  _MyWidgetState createState() => _MyWidgetState();


class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin 
  AnimationController _controller;

  
  void initState() 
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 1),
      vsync: this,
    )..repeat();
  

  
  void dispose() 
    _controller.dispose();
    super.dispose();
  

  
  Widget build(BuildContext context) 
    return Container(
      child: Center(
        child: RotationTransition(
          turns: Tween(begin: 0.0, end: 1.0).animate(_controller),
          child: Icon(Icons.cached),
        ),
      ),
    );
  

在上面的示例中,我们使用了 SingleTickerProviderStateMixin 来管理 _controller,这样我们就可以在 initState 中将它与 TickerProvider 关联起来。

Flutter `showModalBottomSheet` Ticker 在测试期间未处理

【中文标题】Flutter `showModalBottomSheet` Ticker 在测试期间未处理【英文标题】:Flutter `showModalBottomSheet` Ticker was not disposed during tests 【发布时间】:2019-12-26 01:27:32 【问题描述】:

在编写 Flutter 小部件测试时,我遇到了一个错误,即在 showModalBottomSheet() 期间创建的 Ticker 没有被释放。

我想我明白如果我要实现自己的 Flutter 动画,我应该制作一个 AnimationController,我会在小部件 dispose 方法期间调用 AnimationController.dispose()

但是,由于(我相信)AnimationController 被抽象出来以提供一些便利,所以我不确定在测试完成后在哪里或如何确保小部件被处理掉。

注意:代码有效,当我在模拟器/模拟器上进行测试时,模态底页很棒。我只是希望能够在testWidgets 测试中对其进行测试。

我查看了 showModalBottomSheet 文档https://api.flutter.dev/flutter/material/showModalBottomSheet.html,但这仅显示了如何使用该功能。我没有找到任何方法来控制何时可以处理动画。

test_test.dart

(额外的pump() 以防异步问题,但似乎没有帮助)

testWidgets('Taping edit score button brings up bottom sheet to edit',
      (WidgetTester tester) async 
    setUp();
    await tester.pumpWidget(MaterialApp(
      home: GameList(
        game: Game(players: players),
      ),
    ));
    await tester.tap(find.byKey(Key('p1-edit-score')));
    await tester.pump();
    await tester.pump();
    expect(find.byKey(Key('test')), findsOneWidget);
    await tester.tap(find.byKey(Key('tap-me')));
    await tester.pump();
    await tester.pump();
    tearDown();
  );

game_view.dart


class GameView extends StatefulWidget 
  GameView(@required this.playerList, @required this.onResetPlayerScores);

  final Function onResetPlayerScores;
  final List<Player> playerList;

  @override
  _GameState createState() => _GameState();


class _GameState extends State<GameView> 
  @override
  Widget build(BuildContext context) 
    return Scaffold(
      appBar: AppBar(...),
      body: GameList(
        game: Game(players: widget.playerList),
      ),
    );
  


game_list.dart

为了简洁,去掉了一些小部件

class GameList extends StatefulWidget 
  GameList(@required this.game);

  final Game game;

  @override
  GameListState createState() => GameListState(game: game);


class GameListState extends State<GameList> 
  GameListState(@required this.game);

  final Game game;

  @override
  Widget build(BuildContext context) 
    return ListView.separated(
      shrinkWrap: true,
      itemCount: game.players.length,
      itemBuilder: (BuildContext context, int index) 
        return Card(
          child: Column(
            children: <Widget>[
              ListTile(
                trailing: Text(
                  '$game.players[index].score.toInt()',
                ),
                title: Text(
                  '$game.players[index].name',
                ),
              ),
              Row(
                children: <Widget>[
                  FlatButton(
                    child: Icon(
                      Icons.edit,
                      color: Colors.grey[700],
                    ),
                    onPressed: () async 
                      _settingModalBottomSheet(context);
                    ,
                    key: Key(
                      '$game.players[index].name-edit-score',
                    ),
                  ),
                ],
              ),
            ],
          ),
        );
      ,
      separatorBuilder: (BuildContext context, int index) => const Divider(),
    );
  


void _settingModalBottomSheet(context) 
  showModalBottomSheet(
    context: context,
    builder: (BuildContext buildContext) 
      return Center(
        child: Container(
          child: Wrap(
            children: <Widget>[
              Text(
                'edit',
                key: Key('test'),
              ),
              ListTile(
                leading: Icon(Icons.edit),
                title: Text('Video'),
                onTap: () 
                  Navigator.pop(context, 'video');
                ,
                key: Key('tap-me'),
              ),
            ],
          ),
        ),
      );
    ,
  );

flutter test 的短输出。我认为最重要的部分。

OverlayState created a Ticker via its TickerProviderStateMixin, but at the time dispose() was called
on the mixin, that Ticker was still active. All Tickers must be disposed before calling
super.dispose(). Tickers used by AnimationControllers should be disposed by calling dispose() on the
AnimationController itself. Otherwise, the ticker will leak.

flutter test的完整输出

The following assertion was thrown while finalizing the widget tree:
OverlayState#bfe06(tickers: tracking 1 ticker, entries: [OverlayEntry#c26ee(opaque: false;
maintainState: false), OverlayEntry#7cd4f(opaque: false; maintainState: true),
OverlayEntry#d496e(opaque: false; maintainState: false), OverlayEntry#e9ad3(opaque: false;
maintainState: true)]) was disposed with an active Ticker.
OverlayState created a Ticker via its TickerProviderStateMixin, but at the time dispose() was called
on the mixin, that Ticker was still active. All Tickers must be disposed before calling
super.dispose(). Tickers used by AnimationControllers should be disposed by calling dispose() on the
AnimationController itself. Otherwise, the ticker will leak.
The offending ticker was: _WidgetTicker(created by OverlayState#bfe06(tickers: tracking 0 tickers,
entries: [OverlayEntry#c26ee(opaque: false; maintainState: false), OverlayEntry#7cd4f(opaque: false;
maintainState: true)]))
The stack trace when the _WidgetTicker was actually created was:
#0      new Ticker.<anonymous closure> (package:flutter/src/scheduler/ticker.dart:64:40)
#1      new Ticker (package:flutter/src/scheduler/ticker.dart:66:6)
#2      new _WidgetTicker (package:flutter/src/widgets/ticker_provider.dart:225:80)
#3      _OverlayState&State&TickerProviderStateMixin.createTicker
(package:flutter/src/widgets/ticker_provider.dart:161:34)
#4      new AnimationController (package:flutter/src/animation/animation_controller.dart:245:21)
#5      BottomSheet.createAnimationController
(package:flutter/src/material/bottom_sheet.dart:128:12)
#6      _ModalBottomSheetRoute.createAnimationController
(package:flutter/src/material/bottom_sheet.dart:356:40)
#7      TransitionRoute.install (package:flutter/src/widgets/routes.dart:176:19)
#8      ModalRoute.install (package:flutter/src/widgets/routes.dart:907:11)
#9      NavigatorState.push (package:flutter/src/widgets/navigator.dart:1754:11)
#10     Navigator.push (package:flutter/src/widgets/navigator.dart:1093:34)
#11     showModalBottomSheet (package:flutter/src/material/bottom_sheet.dart:427:20)
#12     _settingModalBottomSheet (package:score_keeper/game_list.dart:98:3)
#13     GameListState.build.<anonymous closure>.<anonymous closure>
(package:score_keeper/game_list.dart:65:23)
<asynchronous suspension>
#14     _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:635:14)
#15     _InkResponseState.build.<anonymous closure>
(package:flutter/src/material/ink_well.dart:711:32)
#16     GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
#17     TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:365:11)
#18     TapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:312:7)
#19     GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:156:27)
#20
_TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding&GestureBinding.handleEvent
(package:flutter/src/gestures/binding.dart:222:20)
#21
_TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding&GestureBinding.dispatchEvent
(package:flutter/src/gestures/binding.dart:198:22)
#22     TestWidgetsFlutterBinding.dispatchEvent (package:flutter_test/src/binding.dart:365:11)
#23     WidgetTester.sendEventToBinding.<anonymous closure>
(package:flutter_test/src/widget_tester.dart:458:15)
#25     WidgetTester.sendEventToBinding.<anonymous closure>
(package:flutter_test/src/widget_tester.dart:457:39)
#28     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:69:41)
#29     WidgetTester.sendEventToBinding (package:flutter_test/src/widget_tester.dart:457:27)
#30     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:422:13)
#32     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:420:39)
#35     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:69:41)
#36     TestGesture.up (package:flutter_test/src/test_pointer.dart:420:27)
#37     WidgetController.tapAt.<anonymous closure> (package:flutter_test/src/controller.dart:263:21)
#51     WidgetController.startGesture (package:flutter_test/src/controller.dart)
#75     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure>
(package:flutter_test/src/binding.dart:1026:17)
#77     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure>
(package:flutter_test/src/binding.dart:1014:35)
(elided 58 frames from class _FakeAsync, package dart:async, package dart:async-patch, and package
stack_trace)


When the exception was thrown, this was the stack:
#0      _OverlayState&State&TickerProviderStateMixin.dispose.<anonymous closure> (package:flutter/src/widgets/ticker_provider.dart:178:13)
#1      _OverlayState&State&TickerProviderStateMixin.dispose (package:flutter/src/widgets/ticker_provider.dart:191:6)
#2      StatefulElement.unmount (package:flutter/src/widgets/framework.dart:4107:12)
#3      _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1737:13)
#4      _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#5      ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#6      _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#7      _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#8      SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)
#9      _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#10     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#11     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#12     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#13     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#14     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)
#15     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#16     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#17     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)
#18     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#19     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#20     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#21     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#22     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#23     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#24     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#25     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#26     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#27     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#28     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#29     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#30     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#31     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#32     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#33     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#34     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#35     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#36     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#37     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#38     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#39     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#40     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#41     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#42     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#43     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#44     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#45     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#46     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#47     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#48     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#49     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#50     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)
#51     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#52     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#53     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#54     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#55     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#56     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#57     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#58     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#59     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#60     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#61     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#62     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#63     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#64     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#65     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#66     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#67     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#68     SingleChildRenderObjectElement.visitChildren (package:flutter/src/widgets/framework.dart:5080:14)
#69     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#70     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#71     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#72     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#73     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#74     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#75     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#76     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#77     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#78     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#79     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#80     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#81     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#82     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#83     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#84     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#85     _InactiveElements._unmount.<anonymous closure> (package:flutter/src/widgets/framework.dart:1735:7)
#86     ComponentElement.visitChildren (package:flutter/src/widgets/framework.dart:3955:14)
#87     _InactiveElements._unmount (package:flutter/src/widgets/framework.dart:1733:13)
#88     ListIterable.forEach (dart:_internal/iterable.dart:39:13)
#89     _InactiveElements._unmountAll (package:flutter/src/widgets/framework.dart:1746:25)
#90     BuildOwner.finalizeTree.<anonymous closure> (package:flutter/src/widgets/framework.dart:2426:27)
#91     BuildOwner.lockState (package:flutter/src/widgets/framework.dart:2258:15)
#92     BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:2425:7)
#93     AutomatedTestWidgetsFlutterBinding.drawFrame (package:flutter_test/src/binding.dart:953:18)
#94     _TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding&GestureBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:285:5)
#95     _TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1016:15)
#96     _TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:958:9)
#97     AutomatedTestWidgetsFlutterBinding.scheduleWarmUpFrame (package:flutter_test/src/binding.dart:915:5)
#98     runApp (package:flutter/src/widgets/binding.dart:787:7)
#99     TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:637:7)
<asynchronous suspension>
#102    TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:613:14)
#103    AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:1010:24)
#109    AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1007:15)
#110    testWidgets.<anonymous closure> (package:flutter_test/src/widget_tester.dart:116:22)
#111    Declarer.test.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:168:27)
<asynchronous suspension>
#112    Invoker.waitForOutstandingCallbacks.<anonymous closure> (package:test_api/src/backend/invoker.dart:250:15)
<asynchronous suspension>
#117    Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:247:5)
#118    Declarer.test.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/declarer.dart:166:33)
#123    Declarer.test.<anonymous closure> (package:test_api/src/backend/declarer.dart:165:13)
<asynchronous suspension>
#124    Invoker._onRun.<anonymous closure>.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:test_api/src/backend/invoker.dart:400:25)
<asynchronous suspension>
#138    _Timer._runTimers (dart:isolate-patch/timer_impl.dart:382:19)
#139    _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:416:5)
#140    _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:172:12)
(elided 28 frames from class _FakeAsync, package dart:async, package dart:async-patch, and package stack_trace)
════════════════════════════════════════════════════════════════════════════════════════════════════
══╡ EXCEPTION CAUGHT BY SCHEDULER LIBRARY ╞═════════════════════════════════════════════════════════
The following message was thrown:
An animation is still running even after the widget tree was disposed.

There was one transient callback left. The stack trace for when it was registered is as follows:
── callback 72 ──
#0      new _FrameCallbackEntry.<anonymous closure> (package:flutter/src/scheduler/binding.dart:112:33)
#1      new _FrameCallbackEntry (package:flutter/src/scheduler/binding.dart:115:6)
#2      _TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding.scheduleFrameCallback (package:flutter/src/scheduler/binding.dart:459:49)
#3      Ticker.scheduleTick (package:flutter/src/scheduler/ticker.dart:243:46)
#4      Ticker.start (package:flutter/src/scheduler/ticker.dart:159:7)
#5      AnimationController._startSimulation (package:flutter/src/animation/animation_controller.dart:685:41)
#6      AnimationController._animateToInternal (package:flutter/src/animation/animation_controller.dart:590:12)
#7      AnimationController.forward (package:flutter/src/animation/animation_controller.dart:458:12)
#8      TransitionRoute.didPush (package:flutter/src/widgets/routes.dart:188:24)
#9      ModalRoute.didPush (package:flutter/src/widgets/routes.dart:917:18)
#10     NavigatorState.push (package:flutter/src/widgets/navigator.dart:1756:11)
#11     Navigator.push (package:flutter/src/widgets/navigator.dart:1093:34)
#12     showModalBottomSheet (package:flutter/src/material/bottom_sheet.dart:427:20)
#13     _settingModalBottomSheet (package:score_keeper/game_list.dart:98:3)
#14     GameListState.build.<anonymous closure>.<anonymous closure> (package:score_keeper/game_list.dart:65:23)
<asynchronous suspension>
#15     _InkResponseState._handleTap (package:flutter/src/material/ink_well.dart:635:14)
#16     _InkResponseState.build.<anonymous closure> (package:flutter/src/material/ink_well.dart:711:32)
#17     GestureRecognizer.invokeCallback (package:flutter/src/gestures/recognizer.dart:182:24)
#18     TapGestureRecognizer._checkUp (package:flutter/src/gestures/tap.dart:365:11)
#19     TapGestureRecognizer.acceptGesture (package:flutter/src/gestures/tap.dart:312:7)
#20     GestureArenaManager.sweep (package:flutter/src/gestures/arena.dart:156:27)
#21     _TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding&GestureBinding.handleEvent (package:flutter/src/gestures/binding.dart:222:20)
#22     _TestWidgetsFlutterBinding&BindingBase&ServicesBinding&SchedulerBinding&GestureBinding.dispatchEvent (package:flutter/src/gestures/binding.dart:198:22)
#23     TestWidgetsFlutterBinding.dispatchEvent (package:flutter_test/src/binding.dart:365:11)
#24     WidgetTester.sendEventToBinding.<anonymous closure> (package:flutter_test/src/widget_tester.dart:458:15)
#26     WidgetTester.sendEventToBinding.<anonymous closure> (package:flutter_test/src/widget_tester.dart:457:39)
#29     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:69:41)
#30     WidgetTester.sendEventToBinding (package:flutter_test/src/widget_tester.dart:457:27)
#31     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:422:13)
#33     TestGesture.up.<anonymous closure> (package:flutter_test/src/test_pointer.dart:420:39)
#36     TestAsyncUtils.guard (package:flutter_test/src/test_async_utils.dart:69:41)
#37     TestGesture.up (package:flutter_test/src/test_pointer.dart:420:27)
#38     WidgetController.tapAt.<anonymous closure> (package:flutter_test/src/controller.dart:263:21)
#52     WidgetController.startGesture (package:flutter_test/src/controller.dart)
#76     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:1026:17)
#78     AutomatedTestWidgetsFlutterBinding.runTest.<anonymous closure> (package:flutter_test/src/binding.dart:1014:35)
(elided 58 frames from class _FakeAsync, package dart:async, package dart:async-patch, and package stack_trace)
════════════════════════════════════════════════════════════════════════════════════════════════════
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following message was thrown:
Multiple exceptions (2) were detected during the running of the current test, and at least one was
unexpected.
════════════════════════════════════════════════════════════════════════════════════════════════════
00:04 +25 -1: /Users/tsustare/src/projects/flutter-score-keeper/score_keeper/test/game_view_test.dart: Taping edit score button brings up bottom sheet to edit [E]
  Test failed. See exception logs above.
  The test description was: Taping edit score button brings up bottom sheet to edit```

【问题讨论】:

【参考方案1】:

尝试点按不在底部工作表中的任何可见小部件,并在测试结束前触发一个框架。这将关闭底部工作表并在测试退出之前将其处理掉 AnimationContoller

例如在测试结束时:

await tester.tap(find.byType(BackButtonIcon)); // any widget that isn't in the bottom sheet
await tester.pump();

【讨论】:

【参考方案2】:

我在测试时也有类似的行为。修改后解决

 await tester.pump();

 await tester.pumpAndSettle();

【讨论】:

这是最干净的解决方案!

以上是关于Flutter Ticker 动画驱动器的主要内容,如果未能解决你的问题,请参考以下文章

Flutter 如何平滑地为标记设置动画?

Flutter修仙指南

Flutte VS RN

Flutter 绘制实践 | 路径篇 - 阴影模糊

Flutter 绘制实践 | 路径篇 - 阴影模糊

flutte学习-编译模式