Skip to content

Commit

Permalink
Align nav bar bottom transition with large title animation (flutter#1…
Browse files Browse the repository at this point in the history
…62097)

Makes the bottom widget sync up with the large title in hero transitions
between nav bars.

## Before


https://github.com/user-attachments/assets/3f8c67c3-20c2-4751-b29b-7db8d3f3409f

## After

https://github.com/user-attachments/assets/5e4c966f-1818-4851-87a1-0bf613ebda0b


## Native searchable-to-searchable:



https://github.com/user-attachments/assets/56cf93e0-e529-4ca8-9f49-4e40f710e5ed

## Flutter searchable-to-searchable:




https://github.com/user-attachments/assets/a98d9f53-8d4b-44cf-afa9-541751c21172







Fixes [CupertinoSliverNavigationBar/CupertinoNavigationBar bottom is not
displayed during nav bar flying hero
transitions](flutter#162203)

## 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.
- [ ] 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].
- [x] 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
  • Loading branch information
victorsanni authored Mar 10, 2025
1 parent ffaec10 commit cd433d4
Show file tree
Hide file tree
Showing 2 changed files with 307 additions and 31 deletions.
135 changes: 109 additions & 26 deletions packages/flutter/lib/src/cupertino/nav_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
userTrailing: widget.trailing,
padding: widget.padding,
userLargeTitle: widget.largeTitle,
userBottom: widget.bottom,
large: widget.largeTitle != null,
staticBar: true, // This one does not scroll
context: context,
Expand Down Expand Up @@ -764,7 +765,8 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
),
),
),
if (widget.bottom != null) SizedBox(height: bottomHeight, child: widget.bottom),
if (widget.bottom != null)
SizedBox(height: bottomHeight, child: components.navBarBottom),
],
),
);
Expand All @@ -775,7 +777,8 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
child: Column(
children: <Widget>[
navBar,
if (widget.bottom != null) SizedBox(height: bottomHeight, child: widget.bottom),
if (widget.bottom != null)
SizedBox(height: bottomHeight, child: components.navBarBottom),
],
),
);
Expand Down Expand Up @@ -1265,6 +1268,23 @@ class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigation
? Visibility(visible: !searchIsActive, child: widget.trailing!)
: null,
userLargeTitle: widget.largeTitle,
userBottom:
(widget._searchable
? searchIsActive
? _ActiveSearchableBottom(
animationController: _animationController,
animation: persistentHeightAnimation,
searchField: widget.searchField,
onSearchFieldTap: _onSearchFieldTap,
)
: _InactiveSearchableBottom(
animationController: _animationController,
animation: persistentHeightAnimation,
searchField: preferredSizeSearchField,
onSearchFieldTap: _onSearchFieldTap,
)
: widget.bottom) ??
const SizedBox.shrink(),
padding: widget.padding,
large: true,
staticBar: false, // This one scrolls.
Expand Down Expand Up @@ -1297,23 +1317,6 @@ class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigation
stretchConfiguration:
widget.stretch && !searchIsActive ? OverScrollHeaderStretchConfiguration() : null,
enableBackgroundFilterBlur: widget.enableBackgroundFilterBlur,
bottom:
(widget._searchable
? searchIsActive
? _ActiveSearchableBottom(
animationController: _animationController,
animation: persistentHeightAnimation,
searchField: widget.searchField,
onSearchFieldTap: _onSearchFieldTap,
)
: _InactiveSearchableBottom(
animationController: _animationController,
animation: persistentHeightAnimation,
searchField: preferredSizeSearchField,
onSearchFieldTap: _onSearchFieldTap,
)
: widget.bottom) ??
const SizedBox.shrink(),
bottomMode:
searchIsActive
? NavigationBarBottomMode.always
Expand Down Expand Up @@ -1347,7 +1350,6 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
required this.alwaysShowMiddle,
required this.stretchConfiguration,
required this.enableBackgroundFilterBlur,
required this.bottom,
required this.bottomMode,
required this.bottomHeight,
required this.controller,
Expand All @@ -1368,7 +1370,6 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
final double largeTitleHeight;
final bool alwaysShowMiddle;
final bool enableBackgroundFilterBlur;
final Widget bottom;
final NavigationBarBottomMode bottomMode;
final double bottomHeight;
final AnimationController controller;
Expand Down Expand Up @@ -1482,14 +1483,14 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
bottom: 0.0,
child: SizedBox(
height: bottomHeight * (1.0 - bottomShrinkFactor),
child: ClipRect(child: bottom),
child: ClipRect(child: components.navBarBottom),
),
),
],
),
),
if (bottomMode == NavigationBarBottomMode.always)
SizedBox(height: bottomHeight, child: bottom),
SizedBox(height: bottomHeight, child: components.navBarBottom),
],
),
),
Expand Down Expand Up @@ -1537,7 +1538,6 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
alwaysShowMiddle != oldDelegate.alwaysShowMiddle ||
heroTag != oldDelegate.heroTag ||
enableBackgroundFilterBlur != oldDelegate.enableBackgroundFilterBlur ||
bottom != oldDelegate.bottom ||
bottomMode != oldDelegate.bottomMode ||
bottomHeight != oldDelegate.bottomHeight ||
controller != oldDelegate.controller;
Expand Down Expand Up @@ -1775,7 +1775,8 @@ class _NavigationBarStaticComponentsKeys {
backLabelKey = GlobalKey(debugLabel: 'Back label'),
middleKey = GlobalKey(debugLabel: 'Middle'),
trailingKey = GlobalKey(debugLabel: 'Trailing'),
largeTitleKey = GlobalKey(debugLabel: 'Large title');
largeTitleKey = GlobalKey(debugLabel: 'Large title'),
navBarBottomKey = GlobalKey(debugLabel: 'Navigation bar bottom');

final GlobalKey navBarBoxKey;
final GlobalKey leadingKey;
Expand All @@ -1784,6 +1785,7 @@ class _NavigationBarStaticComponentsKeys {
final GlobalKey middleKey;
final GlobalKey trailingKey;
final GlobalKey largeTitleKey;
final GlobalKey navBarBottomKey;
}

// Based on various user Widgets and other parameters, construct KeyedSubtree
Expand All @@ -1802,6 +1804,7 @@ class _NavigationBarStaticComponents {
required Widget? userMiddle,
required Widget? userTrailing,
required Widget? userLargeTitle,
required Widget? userBottom,
required EdgeInsetsDirectional? padding,
required bool large,
required bool staticBar,
Expand Down Expand Up @@ -1847,6 +1850,10 @@ class _NavigationBarStaticComponents {
route: route,
automaticImplyTitle: automaticallyImplyTitle,
large: large,
),
navBarBottom = createNavBarBottom(
navBarBottomKey: keys.navBarBottomKey,
userBottom: userBottom,
);

static Widget? _derivedTitle({
Expand Down Expand Up @@ -2024,6 +2031,14 @@ class _NavigationBarStaticComponents {

return KeyedSubtree(key: largeTitleKey, child: largeTitleContent!);
}

final KeyedSubtree? navBarBottom;
static KeyedSubtree? createNavBarBottom({
required GlobalKey navBarBottomKey,
required Widget? userBottom,
}) {
return KeyedSubtree(key: navBarBottomKey, child: userBottom ?? const SizedBox.shrink());
}
}

/// A nav bar back button typically used in [CupertinoNavigationBar].
Expand Down Expand Up @@ -2517,13 +2532,15 @@ class _NavigationBarTransition extends StatelessWidget {
if (componentsTransition.bottomMiddle != null) componentsTransition.bottomMiddle!,
if (componentsTransition.bottomLargeTitle != null) componentsTransition.bottomLargeTitle!,
if (componentsTransition.bottomTrailing != null) componentsTransition.bottomTrailing!,
if (componentsTransition.bottomNavBarBottom != null) componentsTransition.bottomNavBarBottom!,
// Draw top components on top of the bottom components.
if (componentsTransition.topLeading != null) componentsTransition.topLeading!,
if (componentsTransition.topBackChevron != null) componentsTransition.topBackChevron!,
if (componentsTransition.topBackLabel != null) componentsTransition.topBackLabel!,
if (componentsTransition.topMiddle != null) componentsTransition.topMiddle!,
if (componentsTransition.topLargeTitle != null) componentsTransition.topLargeTitle!,
if (componentsTransition.topTrailing != null) componentsTransition.topTrailing!,
if (componentsTransition.topNavBarBottom != null) componentsTransition.topNavBarBottom!,
];

// The actual outer box is big enough to contain both the bottom and top
Expand Down Expand Up @@ -2897,6 +2914,39 @@ class _NavigationBarComponentsTransition {
);
}

Widget? get bottomNavBarBottom {
final KeyedSubtree? bottomNavBarBottom =
bottomComponents.navBarBottomKey.currentWidget as KeyedSubtree?;
final KeyedSubtree? topNavBarBottom =
topComponents.navBarBottomKey.currentWidget as KeyedSubtree?;

if (bottomNavBarBottom == null) {
return null;
}

final RelativeRect from = positionInTransitionBox(
bottomComponents.navBarBottomKey,
from: bottomNavBarBox,
);
// Shift in from the leading edge of the screen.
final RelativeRectTween positionTween = RelativeRectTween(
begin: from,
end: from.shift(Offset(-forwardDirection * bottomNavBarBox.size.width, 0.0)),
);

Widget child = bottomNavBarBottom.child;

// Fade out only if this is not a CupertinoSliverNavigationBar.search to
// CupertinoSliverNavigationBar.search transition.
if (topNavBarBottom == null ||
topNavBarBottom.child is! _InactiveSearchableBottom ||
bottomNavBarBottom.child is! _InactiveSearchableBottom) {
child = FadeTransition(opacity: fadeOutBy(0.8), child: child);
}

return PositionedTransition(rect: animation.drive(positionTween), child: child);
}

Widget? get topLeading {
final KeyedSubtree? topLeading = topComponents.leadingKey.currentWidget as KeyedSubtree?;

Expand Down Expand Up @@ -3093,7 +3143,7 @@ class _NavigationBarComponentsTransition {
return PositionedTransition(
rect: animation.drive(positionTween),
child: FadeTransition(
opacity: fadeInFrom(0.3),
opacity: fadeInFrom(0.0),
child: DefaultTextStyle(
style: topLargeTitleTextStyle!,
maxLines: 1,
Expand All @@ -3103,6 +3153,39 @@ class _NavigationBarComponentsTransition {
),
);
}

Widget? get topNavBarBottom {
final KeyedSubtree? topNavBarBottom =
topComponents.navBarBottomKey.currentWidget as KeyedSubtree?;
final KeyedSubtree? bottomNavBarBottom =
bottomComponents.navBarBottomKey.currentWidget as KeyedSubtree?;

if (topNavBarBottom == null) {
return null;
}

final RelativeRect to = positionInTransitionBox(
topComponents.navBarBottomKey,
from: topNavBarBox,
);
// Shift in from the trailing edge of the screen.
final RelativeRectTween positionTween = RelativeRectTween(
begin: to.shift(Offset(forwardDirection * topNavBarBox.size.width, 0.0)),
end: to,
);

Widget child = topNavBarBottom.child;

// Fade in only if this is not a CupertinoSliverNavigationBar.search to
// CupertinoSliverNavigationBar.search transition.
if (bottomNavBarBottom == null ||
bottomNavBarBottom.child is! _InactiveSearchableBottom ||
topNavBarBottom.child is! _InactiveSearchableBottom) {
child = FadeTransition(opacity: fadeInFrom(0.0), child: child);
}

return PositionedTransition(rect: animation.drive(positionTween), child: child);
}
}

/// Navigation bars' hero rect tween that will move between the static bars
Expand Down
Loading

0 comments on commit cd433d4

Please sign in to comment.