Add SliverSafeArea widget (#14499)

A SafeArea-like widget that applies a SliverPadding instead of a
Padding.
This commit is contained in:
Chris Bracken 2018-02-06 14:28:58 -08:00 committed by GitHub
parent 66febf2663
commit 35c2267ffe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 250 additions and 54 deletions

View file

@ -20,6 +20,8 @@ import 'media_query.dart';
/// ///
/// See also: /// See also:
/// ///
/// * [SliverSafeArea], for insetting slivers to avoid operating system
/// intrusions.
/// * [Padding], for insetting widgets in general. /// * [Padding], for insetting widgets in general.
/// * [MediaQuery], from which the window padding is obtained. /// * [MediaQuery], from which the window padding is obtained.
/// * [dart:ui.Window.padding], which reports the padding from the operating /// * [dart:ui.Window.padding], which reports the padding from the operating
@ -93,3 +95,88 @@ class SafeArea extends StatelessWidget {
description.add(new FlagProperty('bottom', value: left, ifTrue: 'avoid bottom padding')); description.add(new FlagProperty('bottom', value: left, ifTrue: 'avoid bottom padding'));
} }
} }
/// A sliver that insets another sliver by sufficient padding to avoid
/// intrusions by the operating system.
///
/// For example, this will indent the sliver by enough to avoid the status bar
/// at the top of the screen.
///
/// It will also indent the sliver by the amount necessary to avoid The Notch
/// on the iPhone X, or other similar creative physical features of the
/// display.
///
/// See also:
///
/// * [SafeArea], for insetting widgets to avoid operating system intrusions.
/// * [SliverPadding], for insetting slivers in general.
/// * [MediaQuery], from which the window padding is obtained.
/// * [dart:ui.Window.padding], which reports the padding from the operating
/// system.
class SliverSafeArea extends StatelessWidget {
/// Creates a sliver that avoids operating system interfaces.
///
/// The [left], [top], [right], and [bottom] arguments must not be null.
const SliverSafeArea({
Key key,
this.left: true,
this.top: true,
this.right: true,
this.bottom: true,
@required this.sliver,
}) : assert(left != null),
assert(top != null),
assert(right != null),
assert(bottom != null),
super(key: key);
/// Whether to avoid system intrusions on the left.
final bool left;
/// Whether to avoid system intrusions at the top of the screen, typically the
/// system status bar.
final bool top;
/// Whether to avoid system intrusions on the right.
final bool right;
/// Whether to avoid system intrusions on the bottom side of the screen.
final bool bottom;
/// The sliver below this sliver in the tree.
///
/// The padding on the [MediaQuery] for the [sliver] will be suitably adjusted
/// to zero out any sides that were avoided by this sliver.
final Widget sliver;
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
final EdgeInsets padding = MediaQuery.of(context).padding;
return new SliverPadding(
padding: new EdgeInsets.only(
left: left ? padding.left : 0.0,
top: top ? padding.top : 0.0,
right: right ? padding.right : 0.0,
bottom: bottom ? padding.bottom : 0.0,
),
sliver: new MediaQuery.removePadding(
context: context,
removeLeft: left,
removeTop: top,
removeRight: right,
removeBottom: bottom,
child: sliver,
),
);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder description) {
super.debugFillProperties(description);
description.add(new FlagProperty('left', value: left, ifTrue: 'avoid left padding'));
description.add(new FlagProperty('top', value: left, ifTrue: 'avoid top padding'));
description.add(new FlagProperty('right', value: left, ifTrue: 'avoid right padding'));
description.add(new FlagProperty('bottom', value: left, ifTrue: 'avoid bottom padding'));
}
}

View file

@ -7,66 +7,175 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
void main() { void main() {
testWidgets('SafeArea - basic', (WidgetTester tester) async { group('SafeArea', () {
await tester.pumpWidget( testWidgets('SafeArea - basic', (WidgetTester tester) async {
const MediaQuery( await tester.pumpWidget(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)), const MediaQuery(
child: const SafeArea( data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
left: false,
child: const Placeholder(),
),
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
});
testWidgets('SafeArea - nested', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: const SafeArea(
top: false,
child: const SafeArea( child: const SafeArea(
right: false, left: false,
child: const Placeholder(), child: const Placeholder(),
), ),
), ),
), );
); expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(0.0, 20.0));
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0)); expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0)); });
testWidgets('SafeArea - nested', (WidgetTester tester) async {
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: const SafeArea(
top: false,
child: const SafeArea(
right: false,
child: const Placeholder(),
),
),
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 580.0));
});
testWidgets('SafeArea - changing', (WidgetTester tester) async {
const Widget child = const SafeArea(
bottom: false,
child: const SafeArea(
left: false,
bottom: false,
child: const Placeholder(),
),
);
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)),
child: child,
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 600.0));
await tester.pumpWidget(
const MediaQuery(
data: const MediaQueryData(padding: const EdgeInsets.only(
left: 100.0,
top: 30.0,
right: 0.0,
bottom: 40.0,
)),
child: child,
),
);
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(100.0, 30.0));
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0));
});
}); });
testWidgets('SafeArea - changing', (WidgetTester tester) async { group('SliverSafeArea', () {
const Widget child = const SafeArea( Widget buildWidget(EdgeInsets mediaPadding, Widget sliver) {
bottom: false, return new MediaQuery(
child: const SafeArea( data: new MediaQueryData(padding: mediaPadding),
left: false, child: new Directionality(
textDirection: TextDirection.ltr,
child: new Viewport(
offset: new ViewportOffset.fixed(0.0),
axisDirection: AxisDirection.down,
slivers: <Widget>[
const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('before'))),
sliver,
const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('after'))),
],
),
),
);
}
void verify(WidgetTester tester, List<Rect> expectedRects) {
final List<Rect> testAnswers = tester.renderObjectList<RenderBox>(find.byType(SizedBox)).map<Rect>(
(RenderBox target) {
final Offset topLeft = target.localToGlobal(Offset.zero);
final Offset bottomRight = target.localToGlobal(target.size.bottomRight(Offset.zero));
return new Rect.fromPoints(topLeft, bottomRight);
}
).toList();
expect(testAnswers, equals(expectedRects));
}
testWidgets('SliverSafeArea - basic', (WidgetTester tester) async {
await tester.pumpWidget(
buildWidget(
const EdgeInsets.all(20.0),
const SliverSafeArea(
left: false,
sliver: const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('padded'))),
),
),
);
verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
new Rect.fromLTWH(0.0, 120.0, 780.0, 100.0),
new Rect.fromLTWH(0.0, 240.0, 800.0, 100.0),
]);
});
testWidgets('SliverSafeArea - nested', (WidgetTester tester) async {
await tester.pumpWidget(
buildWidget(
const EdgeInsets.all(20.0),
const SliverSafeArea(
top: false,
sliver: const SliverSafeArea(
right: false,
sliver: const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('padded'))),
),
),
),
);
verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
new Rect.fromLTWH(20.0, 120.0, 760.0, 100.0),
new Rect.fromLTWH(0.0, 240.0, 800.0, 100.0),
]);
});
testWidgets('SliverSafeArea - changing', (WidgetTester tester) async {
const Widget sliver = const SliverSafeArea(
bottom: false, bottom: false,
child: const Placeholder(), sliver: const SliverSafeArea(
), left: false,
); bottom: false,
await tester.pumpWidget( sliver: const SliverToBoxAdapter(child: const SizedBox(width: 800.0, height: 100.0, child: const Text('padded'))),
const MediaQuery( ),
data: const MediaQueryData(padding: const EdgeInsets.all(20.0)), );
child: child, await tester.pumpWidget(
), buildWidget(
); const EdgeInsets.all(20.0),
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(20.0, 20.0)); sliver,
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(780.0, 600.0)); ),
await tester.pumpWidget( );
const MediaQuery( verify(tester, <Rect>[
data: const MediaQueryData(padding: const EdgeInsets.only( new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
left: 100.0, new Rect.fromLTWH(20.0, 120.0, 760.0, 100.0),
top: 30.0, new Rect.fromLTWH(0.0, 220.0, 800.0, 100.0),
right: 0.0, ]);
bottom: 40.0,
)), await tester.pumpWidget(
child: child, buildWidget(
), const EdgeInsets.only(
); left: 100.0,
expect(tester.getTopLeft(find.byType(Placeholder)), const Offset(100.0, 30.0)); top: 30.0,
expect(tester.getBottomRight(find.byType(Placeholder)), const Offset(800.0, 600.0)); right: 0.0,
bottom: 40.0,
),
sliver,
),
);
verify(tester, <Rect>[
new Rect.fromLTWH(0.0, 0.0, 800.0, 100.0),
new Rect.fromLTWH(100.0, 130.0, 700.0, 100.0),
new Rect.fromLTWH(0.0, 230.0, 800.0, 100.0),
]);
});
}); });
} }