diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 09f98a0492..7629cf4143 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -245,12 +245,18 @@ class _FlexibleSpaceBarState extends State { constraints.maxHeight > height) { height = constraints.maxHeight; } + final double topPadding = _getCollapsePadding(t, settings); children.add(Positioned( - top: _getCollapsePadding(t, settings), + top: topPadding, left: 0.0, right: 0.0, height: height, child: Opacity( + // We need the child widget to repaint, however both the opacity + // and potentially `widget.background` can be constant which won't + // lead to repainting. + // see: https://github.com/flutter/flutter/issues/127836 + key: ValueKey(topPadding), // IOS is relying on this semantics node to correctly traverse // through the app bar when it is collapsed. alwaysIncludeSemantics: true, diff --git a/packages/flutter/test/material/flexible_space_bar_test.dart b/packages/flutter/test/material/flexible_space_bar_test.dart index 76d3b55c48..4ccfdac58d 100644 --- a/packages/flutter/test/material/flexible_space_bar_test.dart +++ b/packages/flutter/test/material/flexible_space_bar_test.dart @@ -348,7 +348,7 @@ void main() { rect: const Rect.fromLTRB(0.0, 0.0, 800.0, 56.0), children: [ TestSemantics( - id: 11, + id: 18, rect: const Rect.fromLTRB(0.0, 36.0, 800.0, 92.0), label: 'Expanded title', textDirection: TextDirection.ltr, @@ -409,8 +409,6 @@ void main() { label: 'Item 6', textDirection: TextDirection.ltr, ), - - ], ), ], @@ -786,6 +784,22 @@ void main() { await tester.pumpWidget(buildFrame(TargetPlatform.linux, true)); expect(getTitleBottomLeft(), const Offset(390.0, 0.0)); }); + + testWidgets('FlexibleSpaceBar rebuilds when scrolling.', (WidgetTester tester) async { + await tester.pumpWidget(const MaterialApp( + home: SubCategoryScreenView(), + )); + + expect(RebuildTracker.count, 1); + + // We drag up to fully collapse the space bar. + for (int i = 0; i < 20; i++) { + await tester.drag(find.byKey(SubCategoryScreenView.scrollKey), const Offset(0, -50.0)); + await tester.pumpAndSettle(); + } + + expect(RebuildTracker.count, greaterThan(1)); + }); } class TestDelegate extends SliverPersistentHeaderDelegate { @@ -810,3 +824,76 @@ class TestDelegate extends SliverPersistentHeaderDelegate { @override bool shouldRebuild(TestDelegate oldDelegate) => false; } + +class RebuildTracker extends StatelessWidget { + const RebuildTracker({super.key}); + + static int count = 0; + + @override + Widget build(BuildContext context) { + count++; + return const SizedBox(width: 100, height: 100); + } +} + +class SubCategoryScreenView extends StatefulWidget { + const SubCategoryScreenView({ + super.key, + }); + + static const Key scrollKey = Key('orange box'); + + @override + State createState() => _SubCategoryScreenViewState(); +} + +class _SubCategoryScreenViewState extends State + with TickerProviderStateMixin { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + centerTitle: true, + title: const Text('Test'), + ), + body: CustomScrollView( + key: SubCategoryScreenView.scrollKey, + slivers: [ + SliverAppBar( + leading: const SizedBox(), + expandedHeight: MediaQuery.of(context).size.width / 1.7, + collapsedHeight: 0, + toolbarHeight: 0, + titleSpacing: 0, + leadingWidth: 0, + flexibleSpace: const FlexibleSpaceBar( + background: AspectRatio( + aspectRatio: 1.7, + child: RebuildTracker(), + ), + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 12)), + SliverToBoxAdapter( + child: GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + ), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 300, + itemBuilder: (BuildContext context, int index) { + return Card( + color: Colors.amber, + child: Center(child: Text('$index')), + ); + }, + ), + ), + const SliverToBoxAdapter(child: SizedBox(height: 12)), + ], + ), + ); + } +}