Allow heroes to fly across navigators and restrict Cupertino nav bars to per navigator by default (#23322)

This commit is contained in:
xster 2018-10-26 12:12:13 -07:00 committed by GitHub
parent 87ca3d52a9
commit 17d068d724
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 120 additions and 12 deletions

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
</dict>
</plist>

View File

@ -65,13 +65,33 @@ const TextStyle _kLargeTitleTextStyle = TextStyle(
// There's a single tag for all instances of navigation bars because they can
// all transition between each other (per Navigator) via Hero transitions.
const _HeroTag _defaultHeroTag = _HeroTag();
const _HeroTag _defaultHeroTag = _HeroTag(null);
class _HeroTag {
const _HeroTag();
const _HeroTag(this.navigator);
final NavigatorState navigator;
// Let the Hero tag be described in tree dumps.
@override
String toString() => 'Default Hero tag for Cupertino navigation bars';
String toString() => 'Default Hero tag for Cupertino navigation bars with navigator $navigator';
@override
bool operator ==(Object other) {
if (identical(this, other)) {
return true;
}
if (other.runtimeType != runtimeType) {
return false;
}
final _HeroTag otherTag = other;
return navigator == otherTag.navigator;
}
@override
int get hashCode {
return identityHashCode(navigator);
}
}
TextStyle _navBarItemStyle(Color color) {
@ -331,9 +351,13 @@ class CupertinoNavigationBar extends StatefulWidget implements ObstructingPrefer
/// Tag for the navigation bar's Hero widget if [transitionBetweenRoutes] is true.
///
/// Defaults to a common tag between all [CupertinoNavigationBar] and
/// [CupertinoSliverNavigationBar] instances so they can all transition
/// between each other as long as there's only one per route. Use this tag
/// override with different tags to have multiple navigation bars per route.
/// [CupertinoSliverNavigationBar] instances of the same [Navigator]. With the
/// default tag, all navigation bars of the same navigator can transition
/// between each other as long as there's only one navigation bar per route.
///
/// This [heroTag] can be overridden to manually handle having multiple
/// navigation bars per route or to transition between multiple
/// [Navigator]s.
///
/// Cannot be null. To disable Hero transitions for this navigation bar,
/// set [transitionBetweenRoutes] to false.
@ -398,7 +422,9 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
}
return Hero(
tag: widget.heroTag,
tag: widget.heroTag == _defaultHeroTag
? _HeroTag(Navigator.of(context))
: widget.heroTag,
createRectTween: _linearTranslateWithLargestRectSizeTween,
placeholderBuilder: _navBarHeroLaunchPadBuilder,
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
@ -733,7 +759,9 @@ class _LargeTitleNavigationBarSliverDelegate
}
return Hero(
tag: heroTag,
tag: heroTag == _defaultHeroTag
? _HeroTag(Navigator.of(context))
: heroTag,
createRectTween: _linearTranslateWithLargestRectSizeTween,
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
placeholderBuilder: _navBarHeroLaunchPadBuilder,

View File

@ -222,10 +222,6 @@ class Hero extends StatefulWidget {
result[tag] = heroState;
}
}
// Don't perform transitions across different Navigators.
if (element.widget is Navigator) {
return;
}
element.visitChildren(visitor);
}
context.visitChildElements(visitor);

View File

@ -385,6 +385,82 @@ void main() {
);
});
testWidgets('Multiple nav bars tags do not conflict if in different navigators',
(WidgetTester tester) async {
await tester.pumpWidget(
CupertinoApp(
home: CupertinoTabScaffold(
tabBar: CupertinoTabBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.search),
title: Text('Tab 1'),
),
BottomNavigationBarItem(
icon: Icon(CupertinoIcons.settings),
title: Text('Tab 2'),
),
],
),
tabBuilder: (BuildContext context, int tab) {
return CupertinoTabView(
builder: (BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Tab ${tab + 1} Page 1'),
),
child: Center(
child: CupertinoButton(
child: const Text('Next'),
onPressed: () {
Navigator.push<void>(context, CupertinoPageRoute<void>(
title: 'Tab ${tab + 1} Page 2',
builder: (BuildContext context) {
return const CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(),
child: Placeholder(),
);
}
));
},
),
),
);
},
);
},
),
),
);
await tester.tap(find.text('Tab 2'));
await tester.pump();
expect(find.text('Tab 1 Page 1', skipOffstage: false), findsOneWidget);
expect(find.text('Tab 2 Page 1'), findsOneWidget);
// At this point, there are 2 nav bars seeded with the same _defaultHeroTag.
// But they're inside different navigators.
await tester.tap(find.text('Next'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
// One is inside the flight shuttle and another is invisible in the
// incoming route in case a new flight needs to be created midflight.
expect(find.text('Tab 2 Page 2'), findsNWidgets(2));
await tester.pump(const Duration(milliseconds: 500));
expect(find.text('Tab 2 Page 2'), findsOneWidget);
// Offstaged by tab 2's navigator.
expect(find.text('Tab 2 Page 1', skipOffstage: false), findsOneWidget);
// Offstaged by the CupertinoTabScaffold.
expect(find.text('Tab 1 Page 1', skipOffstage: false), findsOneWidget);
// Never navigated to tab 1 page 2.
expect(find.text('Tab 1 Page 2', skipOffstage: false), findsNothing);
});
testWidgets('Transition box grows to large title size',
(WidgetTester tester) async {
await startTransitionBetween(