diff --git a/lib/widgets/overflow_marquee.dart b/lib/widgets/overflow_marquee.dart index 95dd5772..49822258 100644 --- a/lib/widgets/overflow_marquee.dart +++ b/lib/widgets/overflow_marquee.dart @@ -151,11 +151,12 @@ class _OverflowMarqueeState extends State } return false; }, - child: RepaintBoundary( - child: SingleChildScrollView( - controller: _scroll, - scrollDirection: Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), + child: SingleChildScrollView( + controller: _scroll, + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + // The viewport repaints as it scrolls; retain the unchanged content. + child: RepaintBoundary( child: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/test/overflow_marquee_test.dart b/test/overflow_marquee_test.dart index 98c6de14..3600f95c 100644 --- a/test/overflow_marquee_test.dart +++ b/test/overflow_marquee_test.dart @@ -27,6 +27,7 @@ void main() { bool disableAnimations = false, bool tickersEnabled = true, TextDirection direction = TextDirection.ltr, + CustomPainter? foregroundPainter, }) async { await tester.pumpWidget( MaterialApp( @@ -43,12 +44,15 @@ void main() { width: width, child: OverflowMarquee( resetKey: title, - child: ExplicitTrackTitle( - title: title, - explicit: explicit, - style: const TextStyle(fontSize: 16), - maxLines: 1, - overflow: TextOverflow.ellipsis, + child: CustomPaint( + foregroundPainter: foregroundPainter, + child: ExplicitTrackTitle( + title: title, + explicit: explicit, + style: const TextStyle(fontSize: 16), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), ), ), ), @@ -133,6 +137,38 @@ void main() { expect(tester.binding.transientCallbackCount, 0); }); + testWidgets('scrolling retains text painting and track changes refresh it', ( + tester, + ) async { + final counter = _PaintCounter(); + await pumpTitle(tester, foregroundPainter: counter); + await startScrolling(tester); + final scroll = controller(tester); + final start = scroll.offset; + final paints = counter.paints; + expect(start, greaterThan(0)); + for (var frame = 0; frame < 20; frame++) { + await tester.pump(const Duration(milliseconds: 16)); + } + expect(scroll.offset, greaterThan(start)); + expect(counter.paints, paints); + + await pumpTitle( + tester, + title: '$longTitle updated', + foregroundPainter: counter, + ); + expect(scroll.offset, 0); + expect(counter.paints, greaterThan(paints)); + expect(find.text('$longTitle updated'), findsNWidgets(2)); + await startScrolling(tester); + final updatedPaints = counter.paints; + await tester.pump(const Duration(milliseconds: 100)); + expect(scroll.offset, greaterThan(28)); + expect(counter.paints, updatedPaints); + await tester.pumpWidget(const SizedBox.shrink()); + }); + testWidgets('reduced motion and hidden routes stop the marquee', ( tester, ) async { @@ -359,3 +395,13 @@ void main() { } } } + +class _PaintCounter extends CustomPainter { + int paints = 0; + + @override + void paint(Canvas canvas, Size size) => paints++; + + @override + bool shouldRepaint(_PaintCounter oldDelegate) => false; +}