mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
Add SliverSafeArea widget (#14499)
A SafeArea-like widget that applies a SliverPadding instead of a Padding.
This commit is contained in:
parent
66febf2663
commit
35c2267ffe
|
@ -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'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue