diff --git a/packages/flutter/lib/src/widgets/animated_scroll_view.dart b/packages/flutter/lib/src/widgets/animated_scroll_view.dart index a8555719030..85638313158 100644 --- a/packages/flutter/lib/src/widgets/animated_scroll_view.dart +++ b/packages/flutter/lib/src/widgets/animated_scroll_view.dart @@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart'; import 'basic.dart'; import 'framework.dart'; +import 'media_query.dart'; import 'scroll_controller.dart'; import 'scroll_delegate.dart'; import 'scroll_physics.dart'; @@ -30,6 +31,35 @@ import 'ticker_provider.dart'; /// ** See code in examples/api/lib/widgets/animated_list/animated_list.0.dart ** /// {@end-tool} /// +/// By default, [AnimatedList] will automatically pad the limits of the +/// list's scrollable to avoid partial obstructions indicated by +/// [MediaQuery]'s padding. To avoid this behavior, override with a +/// zero [padding] property. +/// +/// {@tool snippet} +/// The following example demonstrates how to override the default top and +/// bottom padding using [MediaQuery.removePadding]. +/// +/// ```dart +/// Widget myWidget(BuildContext context) { +/// return MediaQuery.removePadding( +/// context: context, +/// removeTop: true, +/// removeBottom: true, +/// child: AnimatedList( +/// initialItemCount: 50, +/// itemBuilder: (BuildContext context, int index, Animation animation) { +/// return Card( +/// color: Colors.amber, +/// child: Center(child: Text('$index')), +/// ); +/// } +/// ), +/// ); +/// } +/// ``` +/// {@end-tool} +/// /// See also: /// /// * [SliverAnimatedList], a sliver that animates items when they are inserted @@ -176,6 +206,7 @@ class AnimatedListState extends _AnimatedScrollViewState { itemBuilder: widget.itemBuilder, initialItemCount: widget.initialItemCount, ), + widget.scrollDirection, ); } } @@ -196,6 +227,38 @@ class AnimatedListState extends _AnimatedScrollViewState { /// ** See code in examples/api/lib/widgets/animated_grid/animated_grid.0.dart ** /// {@end-tool} /// +/// By default, [AnimatedGrid] will automatically pad the limits of the +/// grid's scrollable to avoid partial obstructions indicated by +/// [MediaQuery]'s padding. To avoid this behavior, override with a +/// zero [padding] property. +/// +/// {@tool snippet} +/// The following example demonstrates how to override the default top and +/// bottom padding using [MediaQuery.removePadding]. +/// +/// ```dart +/// Widget myWidget(BuildContext context) { +/// return MediaQuery.removePadding( +/// context: context, +/// removeTop: true, +/// removeBottom: true, +/// child: AnimatedGrid( +/// gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( +/// crossAxisCount: 3, +/// ), +/// initialItemCount: 50, +/// itemBuilder: (BuildContext context, int index, Animation animation) { +/// return Card( +/// color: Colors.amber, +/// child: Center(child: Text('$index')), +/// ); +/// } +/// ), +/// ); +/// } +/// ``` +/// {@end-tool} +/// /// See also: /// /// * [SliverAnimatedGrid], a sliver which animates items when they are inserted @@ -353,6 +416,7 @@ class AnimatedGridState extends _AnimatedScrollViewState { itemBuilder: widget.itemBuilder, initialItemCount: widget.initialItemCount, ), + widget.scrollDirection, ); } } @@ -529,7 +593,35 @@ abstract class _AnimatedScrollViewState extends S _sliverAnimatedMultiBoxKey.currentState!.removeAllItems(builder, duration: duration); } - Widget _wrap(Widget sliver) { + Widget _wrap(Widget sliver, Axis direction) { + EdgeInsetsGeometry? effectivePadding = widget.padding; + if (widget.padding == null) { + final MediaQueryData? mediaQuery = MediaQuery.maybeOf(context); + if (mediaQuery != null) { + // Automatically pad sliver with padding from MediaQuery. + final EdgeInsets mediaQueryHorizontalPadding = + mediaQuery.padding.copyWith(top: 0.0, bottom: 0.0); + final EdgeInsets mediaQueryVerticalPadding = + mediaQuery.padding.copyWith(left: 0.0, right: 0.0); + // Consume the main axis padding with SliverPadding. + effectivePadding = direction == Axis.vertical + ? mediaQueryVerticalPadding + : mediaQueryHorizontalPadding; + // Leave behind the cross axis padding. + sliver = MediaQuery( + data: mediaQuery.copyWith( + padding: direction == Axis.vertical + ? mediaQueryHorizontalPadding + : mediaQueryVerticalPadding, + ), + child: sliver, + ); + } + } + + if (effectivePadding != null) { + sliver = SliverPadding(padding: effectivePadding, sliver: sliver); + } return CustomScrollView( scrollDirection: widget.scrollDirection, reverse: widget.reverse, @@ -538,12 +630,7 @@ abstract class _AnimatedScrollViewState extends S physics: widget.physics, clipBehavior: widget.clipBehavior, shrinkWrap: widget.shrinkWrap, - slivers: [ - SliverPadding( - padding: widget.padding ?? EdgeInsets.zero, - sliver: sliver, - ), - ], + slivers: [ sliver ], ); } } diff --git a/packages/flutter/test/widgets/animated_grid_test.dart b/packages/flutter/test/widgets/animated_grid_test.dart index 7a86f650813..dcec143a9e7 100644 --- a/packages/flutter/test/widgets/animated_grid_test.dart +++ b/packages/flutter/test/widgets/animated_grid_test.dart @@ -646,6 +646,45 @@ void main() { expect(tester.widget(find.byType(CustomScrollView)).clipBehavior, clipBehavior); }); + + testWidgets('AnimatedGrid applies MediaQuery padding', (WidgetTester tester) async { + const EdgeInsets padding = EdgeInsets.all(30.0); + EdgeInsets? innerMediaQueryPadding; + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.all(30.0), + ), + child: AnimatedGrid( + initialItemCount: 6, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + ), + itemBuilder: (BuildContext context, int index, Animation animation) { + innerMediaQueryPadding = MediaQuery.paddingOf(context); + return const Placeholder(); + }, + ), + ), + ), + ); + final Offset topLeft = tester.getTopLeft(find.byType(Placeholder).first); + // Automatically apply the top padding into sliver. + expect(topLeft, Offset(0.0, padding.top)); + + // Scroll to the bottom. + await tester.drag(find.byType(AnimatedGrid), const Offset(0.0, -1000.0)); + await tester.pumpAndSettle(); + + final Offset bottomRight = tester.getBottomRight(find.byType(Placeholder).last); + // Automatically apply the bottom padding into sliver. + expect(bottomRight, Offset(800.0, 600.0 - padding.bottom)); + + // Verify that the left/right padding is not applied. + expect(innerMediaQueryPadding, const EdgeInsets.symmetric(horizontal: 30.0)); + }); } class _StatefulListItem extends StatefulWidget { diff --git a/packages/flutter/test/widgets/animated_list_test.dart b/packages/flutter/test/widgets/animated_list_test.dart index 89a6ce6ac92..6e585c9fcd2 100644 --- a/packages/flutter/test/widgets/animated_list_test.dart +++ b/packages/flutter/test/widgets/animated_list_test.dart @@ -649,6 +649,42 @@ void main() { expect(tester.widget(find.byType(CustomScrollView)).shrinkWrap, true); }); + + testWidgets('AnimatedList applies MediaQuery padding', (WidgetTester tester) async { + const EdgeInsets padding = EdgeInsets.all(30.0); + EdgeInsets? innerMediaQueryPadding; + await tester.pumpWidget( + Directionality( + textDirection: TextDirection.ltr, + child: MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.all(30.0), + ), + child: AnimatedList( + initialItemCount: 3, + itemBuilder: (BuildContext context, int index, Animation animation) { + innerMediaQueryPadding = MediaQuery.paddingOf(context); + return const Placeholder(); + }, + ), + ), + ), + ); + final Offset topLeft = tester.getTopLeft(find.byType(Placeholder).first); + // Automatically apply the top padding into sliver. + expect(topLeft, Offset(0.0, padding.top)); + + // Scroll to the bottom. + await tester.drag(find.byType(AnimatedList), const Offset(0.0, -1000.0)); + await tester.pumpAndSettle(); + + final Offset bottomLeft = tester.getBottomLeft(find.byType(Placeholder).last); + // Automatically apply the bottom padding into sliver. + expect(bottomLeft, Offset(0.0, 600.0 - padding.bottom)); + + // Verify that the left/right padding is not applied. + expect(innerMediaQueryPadding, const EdgeInsets.symmetric(horizontal: 30.0)); + }); }