Skip to content

Commit cd433d4

Browse files
authored
Align nav bar bottom transition with large title animation (#162097)
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/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
1 parent ffaec10 commit cd433d4

File tree

2 files changed

+307
-31
lines changed

2 files changed

+307
-31
lines changed

packages/flutter/lib/src/cupertino/nav_bar.dart

Lines changed: 109 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,7 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
728728
userTrailing: widget.trailing,
729729
padding: widget.padding,
730730
userLargeTitle: widget.largeTitle,
731+
userBottom: widget.bottom,
731732
large: widget.largeTitle != null,
732733
staticBar: true, // This one does not scroll
733734
context: context,
@@ -764,7 +765,8 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
764765
),
765766
),
766767
),
767-
if (widget.bottom != null) SizedBox(height: bottomHeight, child: widget.bottom),
768+
if (widget.bottom != null)
769+
SizedBox(height: bottomHeight, child: components.navBarBottom),
768770
],
769771
),
770772
);
@@ -775,7 +777,8 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
775777
child: Column(
776778
children: <Widget>[
777779
navBar,
778-
if (widget.bottom != null) SizedBox(height: bottomHeight, child: widget.bottom),
780+
if (widget.bottom != null)
781+
SizedBox(height: bottomHeight, child: components.navBarBottom),
779782
],
780783
),
781784
);
@@ -1265,6 +1268,23 @@ class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigation
12651268
? Visibility(visible: !searchIsActive, child: widget.trailing!)
12661269
: null,
12671270
userLargeTitle: widget.largeTitle,
1271+
userBottom:
1272+
(widget._searchable
1273+
? searchIsActive
1274+
? _ActiveSearchableBottom(
1275+
animationController: _animationController,
1276+
animation: persistentHeightAnimation,
1277+
searchField: widget.searchField,
1278+
onSearchFieldTap: _onSearchFieldTap,
1279+
)
1280+
: _InactiveSearchableBottom(
1281+
animationController: _animationController,
1282+
animation: persistentHeightAnimation,
1283+
searchField: preferredSizeSearchField,
1284+
onSearchFieldTap: _onSearchFieldTap,
1285+
)
1286+
: widget.bottom) ??
1287+
const SizedBox.shrink(),
12681288
padding: widget.padding,
12691289
large: true,
12701290
staticBar: false, // This one scrolls.
@@ -1297,23 +1317,6 @@ class _CupertinoSliverNavigationBarState extends State<CupertinoSliverNavigation
12971317
stretchConfiguration:
12981318
widget.stretch && !searchIsActive ? OverScrollHeaderStretchConfiguration() : null,
12991319
enableBackgroundFilterBlur: widget.enableBackgroundFilterBlur,
1300-
bottom:
1301-
(widget._searchable
1302-
? searchIsActive
1303-
? _ActiveSearchableBottom(
1304-
animationController: _animationController,
1305-
animation: persistentHeightAnimation,
1306-
searchField: widget.searchField,
1307-
onSearchFieldTap: _onSearchFieldTap,
1308-
)
1309-
: _InactiveSearchableBottom(
1310-
animationController: _animationController,
1311-
animation: persistentHeightAnimation,
1312-
searchField: preferredSizeSearchField,
1313-
onSearchFieldTap: _onSearchFieldTap,
1314-
)
1315-
: widget.bottom) ??
1316-
const SizedBox.shrink(),
13171320
bottomMode:
13181321
searchIsActive
13191322
? NavigationBarBottomMode.always
@@ -1347,7 +1350,6 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
13471350
required this.alwaysShowMiddle,
13481351
required this.stretchConfiguration,
13491352
required this.enableBackgroundFilterBlur,
1350-
required this.bottom,
13511353
required this.bottomMode,
13521354
required this.bottomHeight,
13531355
required this.controller,
@@ -1368,7 +1370,6 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
13681370
final double largeTitleHeight;
13691371
final bool alwaysShowMiddle;
13701372
final bool enableBackgroundFilterBlur;
1371-
final Widget bottom;
13721373
final NavigationBarBottomMode bottomMode;
13731374
final double bottomHeight;
13741375
final AnimationController controller;
@@ -1482,14 +1483,14 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
14821483
bottom: 0.0,
14831484
child: SizedBox(
14841485
height: bottomHeight * (1.0 - bottomShrinkFactor),
1485-
child: ClipRect(child: bottom),
1486+
child: ClipRect(child: components.navBarBottom),
14861487
),
14871488
),
14881489
],
14891490
),
14901491
),
14911492
if (bottomMode == NavigationBarBottomMode.always)
1492-
SizedBox(height: bottomHeight, child: bottom),
1493+
SizedBox(height: bottomHeight, child: components.navBarBottom),
14931494
],
14941495
),
14951496
),
@@ -1537,7 +1538,6 @@ class _LargeTitleNavigationBarSliverDelegate extends SliverPersistentHeaderDeleg
15371538
alwaysShowMiddle != oldDelegate.alwaysShowMiddle ||
15381539
heroTag != oldDelegate.heroTag ||
15391540
enableBackgroundFilterBlur != oldDelegate.enableBackgroundFilterBlur ||
1540-
bottom != oldDelegate.bottom ||
15411541
bottomMode != oldDelegate.bottomMode ||
15421542
bottomHeight != oldDelegate.bottomHeight ||
15431543
controller != oldDelegate.controller;
@@ -1775,7 +1775,8 @@ class _NavigationBarStaticComponentsKeys {
17751775
backLabelKey = GlobalKey(debugLabel: 'Back label'),
17761776
middleKey = GlobalKey(debugLabel: 'Middle'),
17771777
trailingKey = GlobalKey(debugLabel: 'Trailing'),
1778-
largeTitleKey = GlobalKey(debugLabel: 'Large title');
1778+
largeTitleKey = GlobalKey(debugLabel: 'Large title'),
1779+
navBarBottomKey = GlobalKey(debugLabel: 'Navigation bar bottom');
17791780

17801781
final GlobalKey navBarBoxKey;
17811782
final GlobalKey leadingKey;
@@ -1784,6 +1785,7 @@ class _NavigationBarStaticComponentsKeys {
17841785
final GlobalKey middleKey;
17851786
final GlobalKey trailingKey;
17861787
final GlobalKey largeTitleKey;
1788+
final GlobalKey navBarBottomKey;
17871789
}
17881790

17891791
// Based on various user Widgets and other parameters, construct KeyedSubtree
@@ -1802,6 +1804,7 @@ class _NavigationBarStaticComponents {
18021804
required Widget? userMiddle,
18031805
required Widget? userTrailing,
18041806
required Widget? userLargeTitle,
1807+
required Widget? userBottom,
18051808
required EdgeInsetsDirectional? padding,
18061809
required bool large,
18071810
required bool staticBar,
@@ -1847,6 +1850,10 @@ class _NavigationBarStaticComponents {
18471850
route: route,
18481851
automaticImplyTitle: automaticallyImplyTitle,
18491852
large: large,
1853+
),
1854+
navBarBottom = createNavBarBottom(
1855+
navBarBottomKey: keys.navBarBottomKey,
1856+
userBottom: userBottom,
18501857
);
18511858

18521859
static Widget? _derivedTitle({
@@ -2024,6 +2031,14 @@ class _NavigationBarStaticComponents {
20242031

20252032
return KeyedSubtree(key: largeTitleKey, child: largeTitleContent!);
20262033
}
2034+
2035+
final KeyedSubtree? navBarBottom;
2036+
static KeyedSubtree? createNavBarBottom({
2037+
required GlobalKey navBarBottomKey,
2038+
required Widget? userBottom,
2039+
}) {
2040+
return KeyedSubtree(key: navBarBottomKey, child: userBottom ?? const SizedBox.shrink());
2041+
}
20272042
}
20282043

20292044
/// A nav bar back button typically used in [CupertinoNavigationBar].
@@ -2517,13 +2532,15 @@ class _NavigationBarTransition extends StatelessWidget {
25172532
if (componentsTransition.bottomMiddle != null) componentsTransition.bottomMiddle!,
25182533
if (componentsTransition.bottomLargeTitle != null) componentsTransition.bottomLargeTitle!,
25192534
if (componentsTransition.bottomTrailing != null) componentsTransition.bottomTrailing!,
2535+
if (componentsTransition.bottomNavBarBottom != null) componentsTransition.bottomNavBarBottom!,
25202536
// Draw top components on top of the bottom components.
25212537
if (componentsTransition.topLeading != null) componentsTransition.topLeading!,
25222538
if (componentsTransition.topBackChevron != null) componentsTransition.topBackChevron!,
25232539
if (componentsTransition.topBackLabel != null) componentsTransition.topBackLabel!,
25242540
if (componentsTransition.topMiddle != null) componentsTransition.topMiddle!,
25252541
if (componentsTransition.topLargeTitle != null) componentsTransition.topLargeTitle!,
25262542
if (componentsTransition.topTrailing != null) componentsTransition.topTrailing!,
2543+
if (componentsTransition.topNavBarBottom != null) componentsTransition.topNavBarBottom!,
25272544
];
25282545

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

2917+
Widget? get bottomNavBarBottom {
2918+
final KeyedSubtree? bottomNavBarBottom =
2919+
bottomComponents.navBarBottomKey.currentWidget as KeyedSubtree?;
2920+
final KeyedSubtree? topNavBarBottom =
2921+
topComponents.navBarBottomKey.currentWidget as KeyedSubtree?;
2922+
2923+
if (bottomNavBarBottom == null) {
2924+
return null;
2925+
}
2926+
2927+
final RelativeRect from = positionInTransitionBox(
2928+
bottomComponents.navBarBottomKey,
2929+
from: bottomNavBarBox,
2930+
);
2931+
// Shift in from the leading edge of the screen.
2932+
final RelativeRectTween positionTween = RelativeRectTween(
2933+
begin: from,
2934+
end: from.shift(Offset(-forwardDirection * bottomNavBarBox.size.width, 0.0)),
2935+
);
2936+
2937+
Widget child = bottomNavBarBottom.child;
2938+
2939+
// Fade out only if this is not a CupertinoSliverNavigationBar.search to
2940+
// CupertinoSliverNavigationBar.search transition.
2941+
if (topNavBarBottom == null ||
2942+
topNavBarBottom.child is! _InactiveSearchableBottom ||
2943+
bottomNavBarBottom.child is! _InactiveSearchableBottom) {
2944+
child = FadeTransition(opacity: fadeOutBy(0.8), child: child);
2945+
}
2946+
2947+
return PositionedTransition(rect: animation.drive(positionTween), child: child);
2948+
}
2949+
29002950
Widget? get topLeading {
29012951
final KeyedSubtree? topLeading = topComponents.leadingKey.currentWidget as KeyedSubtree?;
29022952

@@ -3093,7 +3143,7 @@ class _NavigationBarComponentsTransition {
30933143
return PositionedTransition(
30943144
rect: animation.drive(positionTween),
30953145
child: FadeTransition(
3096-
opacity: fadeInFrom(0.3),
3146+
opacity: fadeInFrom(0.0),
30973147
child: DefaultTextStyle(
30983148
style: topLargeTitleTextStyle!,
30993149
maxLines: 1,
@@ -3103,6 +3153,39 @@ class _NavigationBarComponentsTransition {
31033153
),
31043154
);
31053155
}
3156+
3157+
Widget? get topNavBarBottom {
3158+
final KeyedSubtree? topNavBarBottom =
3159+
topComponents.navBarBottomKey.currentWidget as KeyedSubtree?;
3160+
final KeyedSubtree? bottomNavBarBottom =
3161+
bottomComponents.navBarBottomKey.currentWidget as KeyedSubtree?;
3162+
3163+
if (topNavBarBottom == null) {
3164+
return null;
3165+
}
3166+
3167+
final RelativeRect to = positionInTransitionBox(
3168+
topComponents.navBarBottomKey,
3169+
from: topNavBarBox,
3170+
);
3171+
// Shift in from the trailing edge of the screen.
3172+
final RelativeRectTween positionTween = RelativeRectTween(
3173+
begin: to.shift(Offset(forwardDirection * topNavBarBox.size.width, 0.0)),
3174+
end: to,
3175+
);
3176+
3177+
Widget child = topNavBarBottom.child;
3178+
3179+
// Fade in only if this is not a CupertinoSliverNavigationBar.search to
3180+
// CupertinoSliverNavigationBar.search transition.
3181+
if (bottomNavBarBottom == null ||
3182+
bottomNavBarBottom.child is! _InactiveSearchableBottom ||
3183+
topNavBarBottom.child is! _InactiveSearchableBottom) {
3184+
child = FadeTransition(opacity: fadeInFrom(0.0), child: child);
3185+
}
3186+
3187+
return PositionedTransition(rect: animation.drive(positionTween), child: child);
3188+
}
31063189
}
31073190

31083191
/// Navigation bars' hero rect tween that will move between the static bars

0 commit comments

Comments
 (0)