Unverified Commit c8e42b47 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Fix `AnimatedList` & `AnimatedGrid` doesn't apply `MediaQuery` padding (#129556)

fixes [AnimatedList does not take SafeArea into account when building the list ](https://github.com/flutter/flutter/issues/129539)

### Description
This PR fixes an issue for `AnimatedList` & `AnimatedGrid` where `MediaQuery` padding isn't applied. See the [source](https://github.com/flutter/flutter/blob/a20db068dd9f72e2e4a35a3ce64f22d47b3d20f7/packages/flutter/lib/src/widgets/scroll_view.dart#L803-L833).

While the  `ListView` or `GridView` applies `MediaQuery` padding to its inner `SliverPadding`.  This is missing from `AnimatedList` & `AnimatedGrid`.

![Digram of ListView applying MediaQuery padding](https://github.com/flutter/flutter/assets/48603081/01917900-cd26-4ca1-8e51-b7dcd1241471)

The fix applies  `MediaQuery` padding to the inner `SliverPadding` in `AnimatedList` & `AnimatedGrid`.

![Digram of AnimatedList & AnimatedGrid applying MediaQuery padding](https://github.com/flutter/flutter/assets/48603081/75d0a0ad-539c-485e-b3c1-770ee187086b)

### Code sample
<details> 
<summary>expand to view the code sample</summary> 

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

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(useMaterial3: true),
      home: const Example(),
    );
  }
}

class Example extends StatelessWidget {
  const Example({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample'),
      ),
      body: Row(
        children: <Widget>[
          Expanded(
            child: Column(
              children: <Widget>[
                const Text('ListView'),
                Expanded(
                  child: ListView.builder(
                    itemCount: 50,
                    itemBuilder: (_, int index) {
                      return ColoredBox(
                        color: Theme.of(context).colorScheme.primaryContainer,
                        child: Center(
                          child: Text('$index', textAlign: TextAlign.center),
                        ),
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
          const VerticalDivider(width: 4),
          Expanded(
            child: Column(
              children: <Widget>[
                const Text('AnimatedList'),
                Expanded(
                  child: AnimatedList(
                    initialItemCount: 50,
                    itemBuilder: (_, int index, __) {
                      return ColoredBox(
                        color: Theme.of(context).colorScheme.primaryContainer,
                        child: Center(
                          child: Text('$index', textAlign: TextAlign.center),
                        ),
                      );
                    },
                  ),
                ),
              ],
            ),
          ),
          const VerticalDivider(width: 4),
          Expanded(
              child: Column(
            children: <Widget>[
              const Text('AnimatedGrid'),
              Expanded(
                child: AnimatedGrid(
                  initialItemCount: 50,
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2,
                  ),
                  itemBuilder: (_, int index, __) {
                    return ColoredBox(
                      color: Theme.of(context).colorScheme.primaryContainer,
                      child: Center(
                        child: Text('$index', textAlign: TextAlign.center),
                      ),
                    );
                  },
                ),
              ),
            ],
          ))
        ],
      ),
    );
  }
}

``` 
	
</details>

### Before
![Before preview image](https://github.com/flutter/flutter/assets/48603081/73954a8a-9d1d-4b9e-b6a3-cae8071f3462)

### After
![After preview image](https://github.com/flutter/flutter/assets/48603081/9f1dc48a-622f-4402-8d5e-8e6e3e150165)
parent bac05895
......@@ -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<double> 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<AnimatedList> {
itemBuilder: widget.itemBuilder,
initialItemCount: widget.initialItemCount,
),
widget.scrollDirection,
);
}
}
......@@ -196,6 +227,38 @@ class AnimatedListState extends _AnimatedScrollViewState<AnimatedList> {
/// ** 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<double> 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<AnimatedGrid> {
itemBuilder: widget.itemBuilder,
initialItemCount: widget.initialItemCount,
),
widget.scrollDirection,
);
}
}
......@@ -529,7 +593,35 @@ abstract class _AnimatedScrollViewState<T extends _AnimatedScrollView> 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<T extends _AnimatedScrollView> extends S
physics: widget.physics,
clipBehavior: widget.clipBehavior,
shrinkWrap: widget.shrinkWrap,
slivers: <Widget>[
SliverPadding(
padding: widget.padding ?? EdgeInsets.zero,
sliver: sliver,
),
],
slivers: <Widget>[ sliver ],
);
}
}
......
......@@ -646,6 +646,45 @@ void main() {
expect(tester.widget<CustomScrollView>(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<double> 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 {
......
......@@ -649,6 +649,42 @@ void main() {
expect(tester.widget<CustomScrollView>(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<double> 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));
});
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment