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
|
/// scaffold, the body can be resized to avoid overlapping the keyboard, which
|
||||||
/// prevents widgets inside the body from being obscured by the keyboard.
|
/// prevents widgets inside the body from being obscured by the keyboard.
|
||||||
///
|
///
|
||||||
/// Defaults to true.
|
/// Defaults to true and cannot be null.
|
||||||
final bool resizeToAvoidBottomInset;
|
final bool resizeToAvoidBottomInset;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -78,6 +78,12 @@ class CupertinoPageScaffold extends StatelessWidget {
|
|||||||
? existingMediaQuery.viewInsets.bottom
|
? existingMediaQuery.viewInsets.bottom
|
||||||
: 0.0;
|
: 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 =
|
final bool fullObstruction =
|
||||||
navigationBar.fullObstruction ?? CupertinoTheme.of(context).barBackgroundColor.alpha == 0xFF;
|
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
|
// down. If translucent, let main content draw behind navigation bar but hint the
|
||||||
// obstructed area.
|
// obstructed area.
|
||||||
if (fullObstruction) {
|
if (fullObstruction) {
|
||||||
paddedContent = Padding(
|
paddedContent = MediaQuery(
|
||||||
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
|
data: existingMediaQuery.copyWith(
|
||||||
child: child,
|
viewInsets: newViewInsets,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
paddedContent = MediaQuery(
|
paddedContent = MediaQuery(
|
||||||
@ -95,6 +106,7 @@ class CupertinoPageScaffold extends StatelessWidget {
|
|||||||
padding: existingMediaQuery.padding.copyWith(
|
padding: existingMediaQuery.padding.copyWith(
|
||||||
top: topPadding,
|
top: topPadding,
|
||||||
),
|
),
|
||||||
|
viewInsets: newViewInsets,
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(bottom: bottomPadding),
|
padding: EdgeInsets.only(bottom: bottomPadding),
|
||||||
|
@ -97,6 +97,8 @@ class CupertinoTabScaffold extends StatefulWidget {
|
|||||||
Key key,
|
Key key,
|
||||||
@required this.tabBar,
|
@required this.tabBar,
|
||||||
@required this.tabBuilder,
|
@required this.tabBuilder,
|
||||||
|
this.backgroundColor,
|
||||||
|
this.resizeToAvoidBottomInset = true,
|
||||||
}) : assert(tabBar != null),
|
}) : assert(tabBar != null),
|
||||||
assert(tabBuilder != null),
|
assert(tabBuilder != null),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
@ -138,6 +140,20 @@ class CupertinoTabScaffold extends StatefulWidget {
|
|||||||
/// Must not be null.
|
/// Must not be null.
|
||||||
final IndexedWidgetBuilder tabBuilder;
|
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
|
@override
|
||||||
_CupertinoTabScaffoldState createState() => _CupertinoTabScaffoldState();
|
_CupertinoTabScaffoldState createState() => _CupertinoTabScaffoldState();
|
||||||
}
|
}
|
||||||
@ -163,15 +179,30 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<Widget> stacked = <Widget>[];
|
final List<Widget> stacked = <Widget>[];
|
||||||
|
|
||||||
|
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
|
||||||
|
MediaQueryData newMediaQuery = MediaQuery.of(context);
|
||||||
|
|
||||||
Widget content = _TabSwitchingView(
|
Widget content = _TabSwitchingView(
|
||||||
currentTabIndex: _currentPage,
|
currentTabIndex: _currentPage,
|
||||||
tabNumber: widget.tabBar.items.length,
|
tabNumber: widget.tabBar.items.length,
|
||||||
tabBuilder: widget.tabBuilder,
|
tabBuilder: widget.tabBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (widget.tabBar != null) {
|
if (widget.resizeToAvoidBottomInset) {
|
||||||
final MediaQueryData existingMediaQuery = MediaQuery.of(context);
|
// 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.
|
// TODO(xster): Use real size after partial layout instead of preferred size.
|
||||||
// https://github.com/flutter/flutter/issues/12912
|
// https://github.com/flutter/flutter/issues/12912
|
||||||
final double bottomPadding =
|
final double bottomPadding =
|
||||||
@ -186,17 +217,19 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
|||||||
child: content,
|
child: content,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content = MediaQuery(
|
newMediaQuery = newMediaQuery.copyWith(
|
||||||
data: existingMediaQuery.copyWith(
|
padding: newMediaQuery.padding.copyWith(
|
||||||
padding: existingMediaQuery.padding.copyWith(
|
bottom: bottomPadding,
|
||||||
bottom: bottomPadding,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
child: content,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content = MediaQuery(
|
||||||
|
data: newMediaQuery,
|
||||||
|
child: content,
|
||||||
|
);
|
||||||
|
|
||||||
// The main content being at the bottom is added to the stack first.
|
// The main content being at the bottom is added to the stack first.
|
||||||
stacked.add(content);
|
stacked.add(content);
|
||||||
|
|
||||||
@ -222,7 +255,7 @@ class _CupertinoTabScaffoldState extends State<CupertinoTabScaffold> {
|
|||||||
|
|
||||||
return DecoratedBox(
|
return DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: CupertinoTheme.of(context).scaffoldBackgroundColor
|
color: widget.backgroundColor ?? CupertinoTheme.of(context).scaffoldBackgroundColor,
|
||||||
),
|
),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: stacked,
|
children: stacked,
|
||||||
|
@ -42,6 +42,7 @@ void main() {
|
|||||||
|
|
||||||
expect(tester.getSize(find.byType(Container)).height, 600.0 - 44.0 - 100.0);
|
expect(tester.getSize(find.byType(Container)).height, 600.0 - 44.0 - 100.0);
|
||||||
|
|
||||||
|
BuildContext childContext;
|
||||||
await tester.pumpWidget(Directionality(
|
await tester.pumpWidget(Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: MediaQuery(
|
child: MediaQuery(
|
||||||
@ -50,12 +51,20 @@ void main() {
|
|||||||
navigationBar: const CupertinoNavigationBar(
|
navigationBar: const CupertinoNavigationBar(
|
||||||
middle: Text('Transparent'),
|
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);
|
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(
|
await tester.pumpWidget(Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
|
@ -312,6 +312,89 @@ void main() {
|
|||||||
));
|
));
|
||||||
expect(tab2.text.style.color, CupertinoColors.destructiveRed);
|
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 }) {
|
CupertinoTabBar _buildTabBar({ int selectedTab = 0 }) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user