mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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](a20db068dd/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`.

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

### 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

### After

This commit is contained in:
parent
bac0589539
commit
c8e42b47f5
@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user