Add ability to maintain bottom view padding in NavigationBar
safe area (#162076)
Fixes [When the on-screen keyboard is open, NavigationBar does not maintainBottomViewPadding in Edge-to-Edge mode](https://github.com/flutter/flutter/issues/159526) ### Description According to the [SafeArea.maintainBottomViewPadding](https://api.flutter.dev/flutter/widgets/SafeArea/maintainBottomViewPadding.html) docs, it is expected that `NavigationBar` will shift when the view padding changes. This PR provides ability to override the `maintainBottomViewPadding` property in the under `SafeArea` in the `NavigationBar`. ### Code Sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( appBar: AppBar( title: const Text('Sample'), ), body: const Center( child: Padding( padding: EdgeInsets.all(16.0), child: TextField( decoration: InputDecoration(border: OutlineInputBorder()), ), ), ), bottomNavigationBar: NavigationBar( maintainBottomViewPadding: true, destinations: const <Widget>[ NavigationDestination(icon: Icon(Icons.favorite_rounded), label: 'Favorite'), NavigationDestination(icon: Icon(Icons.favorite_rounded), label: 'Favorite'), NavigationDestination(icon: Icon(Icons.favorite_rounded), label: 'Favorite') ]), floatingActionButton: FloatingActionButton( onPressed: () {}, child: const Icon(Icons.add), ), ), ); } } ``` </details> ### With `maintainBottomViewPadding: false` (Default) https://github.com/user-attachments/assets/1cea27d4-2d6d-4bca-bb9a-53c0a21fdb4d ### With `maintainBottomViewPadding: true` https://github.com/user-attachments/assets/b6c7487f-e23c-43db-a365-90bf69fa03dd ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
c518949703
commit
1c0b3369cb
@ -2,6 +2,7 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
/// @docImport 'package:flutter/services.dart';
|
||||||
/// @docImport 'bottom_navigation_bar.dart';
|
/// @docImport 'bottom_navigation_bar.dart';
|
||||||
/// @docImport 'navigation_rail.dart';
|
/// @docImport 'navigation_rail.dart';
|
||||||
/// @docImport 'scaffold.dart';
|
/// @docImport 'scaffold.dart';
|
||||||
@ -113,6 +114,7 @@ class NavigationBar extends StatelessWidget {
|
|||||||
this.overlayColor,
|
this.overlayColor,
|
||||||
this.labelTextStyle,
|
this.labelTextStyle,
|
||||||
this.labelPadding,
|
this.labelPadding,
|
||||||
|
this.maintainBottomViewPadding = false,
|
||||||
}) : assert(destinations.length >= 2),
|
}) : assert(destinations.length >= 2),
|
||||||
assert(0 <= selectedIndex && selectedIndex < destinations.length);
|
assert(0 <= selectedIndex && selectedIndex < destinations.length);
|
||||||
|
|
||||||
@ -245,6 +247,24 @@ class NavigationBar extends StatelessWidget {
|
|||||||
/// the top.
|
/// the top.
|
||||||
final EdgeInsetsGeometry? labelPadding;
|
final EdgeInsetsGeometry? labelPadding;
|
||||||
|
|
||||||
|
/// Specifies whether the underlying [SafeArea] should maintain the bottom
|
||||||
|
/// [MediaQueryData.viewPadding] instead of the bottom [MediaQueryData.padding].
|
||||||
|
///
|
||||||
|
/// When true, this will prevent the [NavigationBar] from shifting when opening a
|
||||||
|
/// software keyboard due to the change in the padding value, especially when the
|
||||||
|
/// app uses [SystemUiMode.edgeToEdge], which renders the system bars over the
|
||||||
|
/// application instead of outside it.
|
||||||
|
///
|
||||||
|
/// Defaults to false.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [SafeArea.maintainBottomViewPadding], which specifies whether the [SafeArea]
|
||||||
|
/// should maintain the bottom [MediaQueryData.viewPadding].
|
||||||
|
/// * [SystemUiMode.edgeToEdge], which sets a fullscreen display with status and
|
||||||
|
/// navigation elements rendered over the application.
|
||||||
|
final bool maintainBottomViewPadding;
|
||||||
|
|
||||||
VoidCallback _handleTap(int index) {
|
VoidCallback _handleTap(int index) {
|
||||||
return onDestinationSelected != null ? () => onDestinationSelected!(index) : () {};
|
return onDestinationSelected != null ? () => onDestinationSelected!(index) : () {};
|
||||||
}
|
}
|
||||||
@ -265,6 +285,7 @@ class NavigationBar extends StatelessWidget {
|
|||||||
surfaceTintColor:
|
surfaceTintColor:
|
||||||
surfaceTintColor ?? navigationBarTheme.surfaceTintColor ?? defaults.surfaceTintColor,
|
surfaceTintColor ?? navigationBarTheme.surfaceTintColor ?? defaults.surfaceTintColor,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
|
maintainBottomViewPadding: maintainBottomViewPadding,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: effectiveHeight,
|
height: effectiveHeight,
|
||||||
child: Row(
|
child: Row(
|
||||||
|
@ -1600,6 +1600,37 @@ void main() {
|
|||||||
expect(_getLabelStyle(tester, disabledText).fontSize, equals(disabledTextStyle.fontSize));
|
expect(_getLabelStyle(tester, disabledText).fontSize, equals(disabledTextStyle.fontSize));
|
||||||
expect(_getLabelStyle(tester, disabledText).color, equals(disabledTextStyle.color));
|
expect(_getLabelStyle(tester, disabledText).color, equals(disabledTextStyle.color));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('NavigationBar.maintainBottomViewPadding can consume bottom MediaQuery.padding', (
|
||||||
|
WidgetTester tester,
|
||||||
|
) async {
|
||||||
|
const double bottomPadding = 40;
|
||||||
|
const TextDirection textDirection = TextDirection.ltr;
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Directionality(
|
||||||
|
textDirection: textDirection,
|
||||||
|
child: MediaQuery(
|
||||||
|
data: const MediaQueryData(padding: EdgeInsets.only(bottom: bottomPadding)),
|
||||||
|
child: Scaffold(
|
||||||
|
bottomNavigationBar: NavigationBar(
|
||||||
|
maintainBottomViewPadding: true,
|
||||||
|
destinations: const <Widget>[
|
||||||
|
NavigationDestination(icon: Icon(Icons.ac_unit), label: 'AC'),
|
||||||
|
NavigationDestination(icon: Icon(Icons.access_alarm), label: 'Alarm'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final double safeAreaBottomPadding =
|
||||||
|
tester.widget<Padding>(find.byType(Padding).first).padding.resolve(textDirection).bottom;
|
||||||
|
expect(safeAreaBottomPadding, equals(0));
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildWidget(Widget child, {bool? useMaterial3}) {
|
Widget _buildWidget(Widget child, {bool? useMaterial3}) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user