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
|
||||
// found in the LICENSE file.
|
||||
|
||||
/// @docImport 'package:flutter/services.dart';
|
||||
/// @docImport 'bottom_navigation_bar.dart';
|
||||
/// @docImport 'navigation_rail.dart';
|
||||
/// @docImport 'scaffold.dart';
|
||||
@ -113,6 +114,7 @@ class NavigationBar extends StatelessWidget {
|
||||
this.overlayColor,
|
||||
this.labelTextStyle,
|
||||
this.labelPadding,
|
||||
this.maintainBottomViewPadding = false,
|
||||
}) : assert(destinations.length >= 2),
|
||||
assert(0 <= selectedIndex && selectedIndex < destinations.length);
|
||||
|
||||
@ -245,6 +247,24 @@ class NavigationBar extends StatelessWidget {
|
||||
/// the top.
|
||||
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) {
|
||||
return onDestinationSelected != null ? () => onDestinationSelected!(index) : () {};
|
||||
}
|
||||
@ -265,6 +285,7 @@ class NavigationBar extends StatelessWidget {
|
||||
surfaceTintColor:
|
||||
surfaceTintColor ?? navigationBarTheme.surfaceTintColor ?? defaults.surfaceTintColor,
|
||||
child: SafeArea(
|
||||
maintainBottomViewPadding: maintainBottomViewPadding,
|
||||
child: SizedBox(
|
||||
height: effectiveHeight,
|
||||
child: Row(
|
||||
|
@ -1600,6 +1600,37 @@ void main() {
|
||||
expect(_getLabelStyle(tester, disabledText).fontSize, equals(disabledTextStyle.fontSize));
|
||||
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}) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user