Skip to content

Commit 3d1f041

Browse files
authored
Clip bottom widgets in nav bar transition (flutter#166705)
Fixes [Search field icons shown mid-transition in CupertinoSliverNavigationBar.search .automatic mode when scrolled](flutter#165689) ### Before https://github.com/user-attachments/assets/75fbc069-157e-4ee0-b03d-6f1ceb476892 ### After https://github.com/user-attachments/assets/22443186-2790-496c-9e88-e86d17d28efd
1 parent ddb8116 commit 3d1f041

File tree

2 files changed

+129
-2
lines changed

2 files changed

+129
-2
lines changed

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3001,7 +3001,10 @@ class _NavigationBarComponentsTransition {
30013001
if (topNavBarBottom == null ||
30023002
topNavBarBottom.child is! _InactiveSearchableBottom ||
30033003
bottomNavBarBottom.child is! _InactiveSearchableBottom) {
3004-
child = FadeTransition(opacity: fadeOutBy(0.8, curve: animationCurve), child: child);
3004+
child = FadeTransition(
3005+
opacity: fadeOutBy(0.8, curve: animationCurve),
3006+
child: ClipRect(child: child),
3007+
);
30053008
}
30063009

30073010
return PositionedTransition(
@@ -3325,7 +3328,10 @@ class _NavigationBarComponentsTransition {
33253328
if (bottomNavBarBottom == null ||
33263329
bottomNavBarBottom.child is! _InactiveSearchableBottom ||
33273330
topNavBarBottom.child is! _InactiveSearchableBottom) {
3328-
child = FadeTransition(opacity: fadeInFrom(0.0, curve: animationCurve), child: child);
3331+
child = FadeTransition(
3332+
opacity: fadeInFrom(0.0, curve: animationCurve),
3333+
child: ClipRect(child: child),
3334+
);
33293335
}
33303336

33313337
return PositionedTransition(

packages/flutter/test/cupertino/nav_bar_transition_test.dart

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
// This file is run as part of a reduced test set in CI on Mac and Windows
6+
// machines.
7+
@Tags(<String>['reduced-test-set'])
58
@TestOn('!chrome')
69
library;
710

@@ -1111,6 +1114,124 @@ void main() {
11111114
);
11121115
});
11131116

1117+
testWidgets(
1118+
'CupertinoSliverNavigationBar.bottom clips its contents mid-transition when scrolled',
1119+
(WidgetTester tester) async {
1120+
await tester.pumpWidget(
1121+
CupertinoApp(
1122+
builder: (BuildContext context, Widget? navigator) {
1123+
return navigator!;
1124+
},
1125+
home: const Placeholder(),
1126+
),
1127+
);
1128+
1129+
tester
1130+
.state<NavigatorState>(find.byType(Navigator))
1131+
.push(
1132+
CupertinoPageRoute<void>(
1133+
title: 'Page 1',
1134+
builder:
1135+
(BuildContext context) =>
1136+
scaffoldForNavBar(
1137+
const CupertinoSliverNavigationBar.search(
1138+
searchField: CupertinoSearchTextField(
1139+
suffixMode: OverlayVisibilityMode.always,
1140+
suffixIcon: Icon(CupertinoIcons.mic_solid),
1141+
),
1142+
),
1143+
)!,
1144+
),
1145+
);
1146+
1147+
await tester.pumpAndSettle();
1148+
1149+
final TestGesture scrollGesture1 = await tester.startGesture(
1150+
tester.getCenter(find.byType(CustomScrollView)),
1151+
);
1152+
await scrollGesture1.moveBy(const Offset(0, -300));
1153+
await scrollGesture1.up();
1154+
await tester.pumpAndSettle();
1155+
1156+
expect(find.byIcon(CupertinoIcons.mic_solid), findsOneWidget);
1157+
expect(find.byIcon(CupertinoIcons.search), findsOneWidget);
1158+
1159+
tester
1160+
.state<NavigatorState>(find.byType(Navigator))
1161+
.push(
1162+
CupertinoPageRoute<void>(
1163+
title: 'Page 2',
1164+
builder:
1165+
(BuildContext context) =>
1166+
scaffoldForNavBar(
1167+
const CupertinoNavigationBar(
1168+
bottom: PreferredSize(
1169+
preferredSize: Size.fromHeight(20),
1170+
child: ColoredBox(color: Color(0xffff0000)),
1171+
),
1172+
),
1173+
)!,
1174+
),
1175+
);
1176+
1177+
await tester.pump();
1178+
await tester.pump(const Duration(milliseconds: 50));
1179+
1180+
expect(find.byIcon(CupertinoIcons.mic_solid), findsNWidgets(2));
1181+
expect(find.byIcon(CupertinoIcons.search), findsNWidgets(2));
1182+
await expectLater(
1183+
find.byType(CupertinoApp),
1184+
matchesGoldenFile('nav_bar_transition.search.bottom.png'),
1185+
);
1186+
await tester.pumpAndSettle();
1187+
1188+
expect(find.byIcon(CupertinoIcons.mic_solid), findsNothing);
1189+
expect(find.byIcon(CupertinoIcons.search), findsNothing);
1190+
1191+
tester
1192+
.state<NavigatorState>(find.byType(Navigator))
1193+
.push(
1194+
CupertinoPageRoute<void>(
1195+
title: 'Page 3',
1196+
builder:
1197+
(BuildContext context) =>
1198+
scaffoldForNavBar(
1199+
const CupertinoSliverNavigationBar.search(
1200+
searchField: CupertinoSearchTextField(
1201+
suffixMode: OverlayVisibilityMode.always,
1202+
suffixIcon: Icon(CupertinoIcons.mic_solid),
1203+
),
1204+
),
1205+
)!,
1206+
),
1207+
);
1208+
1209+
await tester.pumpAndSettle();
1210+
1211+
final TestGesture scrollGesture2 = await tester.startGesture(
1212+
tester.getCenter(find.byType(CustomScrollView)),
1213+
);
1214+
await scrollGesture2.moveBy(const Offset(0, -300));
1215+
await scrollGesture2.up();
1216+
await tester.pumpAndSettle();
1217+
1218+
expect(find.byIcon(CupertinoIcons.mic_solid), findsOneWidget);
1219+
expect(find.byIcon(CupertinoIcons.search), findsOneWidget);
1220+
1221+
tester.state<NavigatorState>(find.byType(Navigator)).pop();
1222+
await tester.pump();
1223+
await tester.pump(const Duration(milliseconds: 50));
1224+
1225+
expect(find.byIcon(CupertinoIcons.mic_solid), findsNWidgets(2));
1226+
expect(find.byIcon(CupertinoIcons.search), findsNWidgets(2));
1227+
await expectLater(
1228+
find.byType(CupertinoApp),
1229+
matchesGoldenFile('nav_bar_transition.search.top.png'),
1230+
);
1231+
await tester.pumpAndSettle();
1232+
},
1233+
);
1234+
11141235
testWidgets('Long title turns into the word back mid transition', (WidgetTester tester) async {
11151236
await startTransitionBetween(
11161237
tester,

0 commit comments

Comments
 (0)