Let CupertinoTabScaffold handle keyboard insets too (#25593)
This commit is contained in:
parent
7a88fbc5fd
commit
d1ec126a3c
@ -57,7 +57,7 @@ class CupertinoPageScaffold extends StatelessWidget {
|
||||
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
|
||||
/// prevents widgets inside the body from being obscured by the keyboard.
|
||||
///
|
||||
/// Defaults to true.
|
||||
/// Defaults to true and cannot be null.
|
||||
final bool resizeToAvoidBottomInset;
|
||||
|
||||
@override
|
||||
@ -78,6 +78,12 @@ class CupertinoPageScaffold extends StatelessWidget {
|
||||
? existingMediaQuery.viewInsets.bottom
|
||||
: 0.0;
|
||||
|
||||
final EdgeInsets newViewInsets = resizeToAvoidBottomInset
|
||||
// The insets are consumed by the scaffolds and no longer exposed to
|
||||
// the descendant subtree.
|
||||
? existingMediaQuery.viewInsets.copyWith(bottom: 0.0)
|
||||
: existingMediaQuery.viewInsets;
|
||||
|
||||
final bool fullObstruction =
|
||||
navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;
|
||||
|
||||
@ -85,9 +91,14 @@ class CupertinoPageScaffold extends StatelessWidget {
|
||||
// down. If translucent, let main content draw behind navigation bar but hint the
|
||||
// obstructed area.
|
||||
if (fullObstruction) {
|
||||
paddedContent = Padding(
|
||||
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
|
||||
child: child,
|
||||
paddedContent = MediaQuery(
|
||||
data: existingMediaQuery.copyWith(
|
||||
viewInsets: newViewInsets,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
paddedContent = MediaQuery(
|
||||
@ -95,6 +106,7 @@ class CupertinoPageScaffold extends StatelessWidget {
|
||||
padding: existingMediaQuery.padding.copyWith(
|
||||
top: topPadding,
|
||||
),
|
||||
viewInsets: newViewInsets,
|
||||
),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||
|
@ -97,6 +97,8 @@ class CupertinoTabScaffold extends StatefulWidget {
|
||||
Key key,
|
||||
@required this.tabBar,
|
||||
@required this.tabBuilder,
|
||||
this.backgroundColor,
|
||||
this.resizeToAvoidBottomInset = true,
|
||||
}) : assert(tabBar != null),
|
||||
assert(tabBuilder != null),
|
||||
super(key: key);
|
||||
@ -138,6 +140,20 @@ class CupertinoTabScaffold extends StatefulWidget {
|
||||
/// Must not be null.
|
||||
final IndexedWidgetBuilder tabBuilder;
|
||||
|
||||
/// The color of the widget that underlies the entire scaffold.
|
||||
///
|
||||
/// By default uses [CupertinoTheme]'s `scaffoldBackgroundColor` when null.
|
||||
final Color backgroundColor;
|
||||
|
||||
/// Whether the [child] should size itself to avoid the window's bottom inset.
|
||||
///
|
||||
/// For example, if there is an onscreen keyboard displayed above the
|
||||
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
|
||||
/// prevents widgets inside the body from being obscured by the keyboard.
|
||||
///
|
||||
/// Defaults to true and cannot be null.
|
||||
final bool resizeToAvoidBottomInset;
|
||||
|
||||
@override
|
||||
_CupertinoTabScaffoldState createState() => _CupertinoTabScaffoldState();
|
||||
}
|
||||
@ -163,15 +179,30 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> stacked = <Widget>[];
|
||||
|
||||
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
|
||||
MediaQueryData newMediaQuery = MediaQuery.of(context);
|
||||
|
||||
Widget content = _TabSwitchingView(
|
||||
currentTabIndex: _currentPage,
|
||||
tabNumber: widget.tabBar.items.length,
|
||||
tabBuilder: widget.tabBuilder,
|
||||
);
|
||||
|
||||
if (widget.tabBar != null) {
|
||||
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
|
||||
if (widget.resizeToAvoidBottomInset) {
|
||||
// Remove the view inset and add it back as a padding in the inner content.
|
||||
newMediaQuery = newMediaQuery.removeViewInsets(removeBottom: true);
|
||||
content = Padding(
|
||||
padding: EdgeInsets.only(bottom: existingMediaQuery.viewInsets.bottom),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.tabBar != null &&
|
||||
// Only pad the content with the height of the tab bar if the tab
|
||||
// isn't already entirely obstructed by a keyboard or other view insets.
|
||||
// Don't double pad.
|
||||
(!widget.resizeToAvoidBottomInset ||
|
||||
widget.tabBar.preferredSize.height > existingMediaQuery.viewInsets.bottom)) {
|
||||
// TODO(xster): Use real size after partial layout instead of preferred size.
|
||||
// https://github.com/flutter/flutter/issues/12912
|
||||
final double bottomPadding =
|
||||
@ -186,17 +217,19 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
||||
child: content,
|
||||
);
|
||||
} else {
|
||||
content = MediaQuery(
|
||||
data: existingMediaQuery.copyWith(
|
||||
padding: existingMediaQuery.padding.copyWith(
|
||||
bottom: bottomPadding,
|
||||
),
|
||||
newMediaQuery = newMediaQuery.copyWith(
|
||||
padding: newMediaQuery.padding.copyWith(
|
||||
bottom: bottomPadding,
|
||||
),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
content = MediaQuery(
|
||||
data: newMediaQuery,
|
||||
child: content,
|
||||
);
|
||||
|
||||
// The main content being at the bottom is added to the stack first.
|
||||
stacked.add(content);
|
||||
|
||||
@ -222,7 +255,7 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: CupertinoTheme.of(context).scaffoldBackgroundColor
|
||||
color: widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
|
||||
),
|
||||
child: Stack(
|
||||
children: stacked,
|
||||
|
@ -42,6 +42,7 @@ void main() {
|
||||
|
||||
expect(tester.getSize(find.byType(Container)).height, 600.0 - 44.0 - 100.0);
|
||||
|
||||
BuildContext childContext;
|
||||
await tester.pumpWidget(Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: MediaQuery(
|
||||
@ -50,12 +51,20 @@ void main() {
|
||||
navigationBar: const CupertinoNavigationBar(
|
||||
middle: Text('Transparent'),
|
||||
),
|
||||
child: Container(),
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
childContext = context;
|
||||
return Container();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
expect(tester.getSize(find.byType(Container)).height, 600.0 - 100.0);
|
||||
// The shouldn't see a media query view inset because it was consumed by
|
||||
// the scaffold.
|
||||
expect(MediaQuery.of(childContext).viewInsets.bottom, 0);
|
||||
|
||||
await tester.pumpWidget(Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
|
@ -312,6 +312,89 @@ void main() {
|
||||
));
|
||||
expect(tab2.text.style.color, CupertinoColors.destructiveRed);
|
||||
});
|
||||
|
||||
testWidgets('Tab contents are padded when there are view insets', (WidgetTester tester) async {
|
||||
BuildContext innerContext;
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(
|
||||
viewInsets: EdgeInsets.only(bottom: 200),
|
||||
),
|
||||
child: CupertinoTabScaffold(
|
||||
tabBar: _buildTabBar(),
|
||||
tabBuilder: (BuildContext context, int index) {
|
||||
innerContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 400));
|
||||
// Don't generate more media query padding from the translucent bottom
|
||||
// tab since the tab is behind the keyboard now.
|
||||
expect(MediaQuery.of(innerContext).padding.bottom, 0);
|
||||
});
|
||||
|
||||
testWidgets('Tab contents are not inset when resizeToAvoidBottomInset overriden', (WidgetTester tester) async {
|
||||
BuildContext innerContext;
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(
|
||||
viewInsets: EdgeInsets.only(bottom: 200),
|
||||
),
|
||||
child: CupertinoTabScaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
tabBar: _buildTabBar(),
|
||||
tabBuilder: (BuildContext context, int index) {
|
||||
innerContext = context;
|
||||
return const Placeholder();
|
||||
}
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 600));
|
||||
// Media query padding shows up in the inner content because it wasn't masked
|
||||
// by the view inset.
|
||||
expect(MediaQuery.of(innerContext).padding.bottom, 50);
|
||||
});
|
||||
|
||||
testWidgets('Tab and page scaffolds do not double stack view insets', (WidgetTester tester) async {
|
||||
BuildContext innerContext;
|
||||
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
home: MediaQuery(
|
||||
data: const MediaQueryData(
|
||||
viewInsets: EdgeInsets.only(bottom: 200),
|
||||
),
|
||||
child: CupertinoTabScaffold(
|
||||
tabBar: _buildTabBar(),
|
||||
tabBuilder: (BuildContext context, int index) {
|
||||
return CupertinoPageScaffold(
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
innerContext = context;
|
||||
return const Placeholder();
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getRect(find.byType(Placeholder)), Rect.fromLTWH(0, 0, 800, 400));
|
||||
expect(MediaQuery.of(innerContext).padding.bottom, 0);
|
||||
});
|
||||
}
|
||||
|
||||
CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user