0

给定一个在 Stream 事件上“摇晃”的小部件

class Shaker extends StatefulWidget {
  final Widget child;
  final Stream<void> stream;
  final Duration duration;
  final int loops;
  const Shaker({
    required this.child,
    required this.stream,
    this.duration = const Duration(milliseconds: 100),
    this.loops = 2,
    Key? key,
  }) : super(key: key);

  @override
  State<Shaker> createState() => _ShakerState();
}

class _ShakerState extends State<Shaker> with SingleTickerProviderStateMixin {
  late final AnimationController _controller;
  late final StreamSubscription _subscription;
  late final Animation<Offset> _offsetAnimation;

  @override
  void initState() {
    _controller = AnimationController(
      vsync: this,
      duration: widget.duration,
    );

    _subscription = widget.stream.listen((event) async {
      for (var i = 0; i < widget.loops; ++i) {
        await _controller.forward();
        await _controller.reverse();
      }
    });

    _offsetAnimation = Tween<Offset>(
      begin: Offset.zero,
      end: const Offset(.02, 0.0),
    ).animate(CurvedAnimation(
          parent: _controller,
          curve: Curves.elasticIn,
        ));
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    _subscription.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SlideTransition(
      position: _offsetAnimation,
      child: widget.child,
    );
  }
}

飞镖板示例

什么是一种优雅的方法来测试收到的事件是否实际发生了“抖动”?
谢谢你

4

1 回答 1

1

在您的小部件测试中,您可以使用WidgetTester.pump()andWidgetTester.pumpAndSettle()来验证此行为。

pump()只是从框架中渲染一个新的帧,但pumpAndSettle()不断地渲染帧,直到没有更多的调度帧,然后返回已调度的帧数。

一个简单(但不太精确)的测试是确保您的动画播放正确的帧数。就像是:

const expectedFramesForShake = 10;

testWidgets('example', (tester) async {
  final controller = StreamController<void>();
  await tester.pumpWidget(Shaker(stream: controller.stream, /* other fields */));
  
  controller.add(null);  // cause a shake
  final frames = await tester.pumpAndSettle();
  expect(frames, expectedFramesForShake); 
});

请注意,您可能必须将小部件包装在 aMaterialApp中以设置必要InheritedWidget的 s 以使测试通过。

这验证了动画播放的时间大约是正确的,但我们可以做得更好!

我们可以在每一帧之后检查小部件树,而不是依赖于pumpAndSettle()返回调度的帧数。

// change this so it matches the values produced by the "correct" animation
const expectedOffsets = [0.0, 1.0, 3.0, 7.0,]; 

testWidgets('better example', (tester) async {
  final controller = StreamController<void>();
  await tester.pumpWidget(Shaker(stream: controller.stream, /* other fields */));

  final offsets = <double>[];  // empty list to store results

  controller.add(null);
  do {
    // pull the SlideTransition from the widget tree
    final transition = tester.widget<SlideTransition>(find.byType<SlideTransition>);

    // read the current value of the position animation
    final horizontalOffset = transition.position.value.dx;
    offsets.add(horizontalOffset);

    // now request a new frame
    await tester.pump();

    // only loop while there are more frames scheduled
  } while (tester.binding.hasScheduledFrame);

  // compare the 2 arrays
  expect(offsets, expectedOffsets);
});

这使您可以确定Shaker每帧将其子项偏移正确的量。但是,这会使您的测试变得非常脆弱,例如,如果您想更改摇动的持续时间,它将更改offsets.

PS 感谢 dartpad 示例 :-)

于 2021-06-28T21:18:37.983 回答