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:
Taha Tesser 2025-01-23 23:00:08 +02:00 committed by GitHub
parent c518949703
commit 1c0b3369cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 52 additions and 0 deletions

View File

@ -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(

View File

@ -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}) {