mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Allow heroes to fly across navigators and restrict Cupertino nav bars to per navigator by default (#23322)
This commit is contained in:
parent
87ca3d52a9
commit
17d068d724
@ -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>
|
@ -65,13 +65,33 @@ const TextStyle _kLargeTitleTextStyle = TextStyle(
|
|||||||
|
|
||||||
// There's a single tag for all instances of navigation bars because they can
|
// There's a single tag for all instances of navigation bars because they can
|
||||||
// all transition between each other (per Navigator) via Hero transitions.
|
// all transition between each other (per Navigator) via Hero transitions.
|
||||||
const _HeroTag _defaultHeroTag = _HeroTag();
|
const _HeroTag _defaultHeroTag = _HeroTag(null);
|
||||||
|
|
||||||
class _HeroTag {
|
class _HeroTag {
|
||||||
const _HeroTag();
|
const _HeroTag(this.navigator);
|
||||||
|
|
||||||
|
final NavigatorState navigator;
|
||||||
|
|
||||||
// Let the Hero tag be described in tree dumps.
|
// Let the Hero tag be described in tree dumps.
|
||||||
@override
|
@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) {
|
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.
|
/// Tag for the navigation bar's Hero widget if [transitionBetweenRoutes] is true.
|
||||||
///
|
///
|
||||||
/// Defaults to a common tag between all [CupertinoNavigationBar] and
|
/// Defaults to a common tag between all [CupertinoNavigationBar] and
|
||||||
/// [CupertinoSliverNavigationBar] instances so they can all transition
|
/// [CupertinoSliverNavigationBar] instances of the same [Navigator]. With the
|
||||||
/// between each other as long as there's only one per route. Use this tag
|
/// default tag, all navigation bars of the same navigator can transition
|
||||||
/// override with different tags to have multiple navigation bars per route.
|
/// 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,
|
/// Cannot be null. To disable Hero transitions for this navigation bar,
|
||||||
/// set [transitionBetweenRoutes] to false.
|
/// set [transitionBetweenRoutes] to false.
|
||||||
@ -398,7 +422,9 @@ class _CupertinoNavigationBarState extends State<CupertinoNavigationBar> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Hero(
|
return Hero(
|
||||||
tag: widget.heroTag,
|
tag: widget.heroTag == _defaultHeroTag
|
||||||
|
? _HeroTag(Navigator.of(context))
|
||||||
|
: widget.heroTag,
|
||||||
createRectTween: _linearTranslateWithLargestRectSizeTween,
|
createRectTween: _linearTranslateWithLargestRectSizeTween,
|
||||||
placeholderBuilder: _navBarHeroLaunchPadBuilder,
|
placeholderBuilder: _navBarHeroLaunchPadBuilder,
|
||||||
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
|
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
|
||||||
@ -733,7 +759,9 @@ class _LargeTitleNavigationBarSliverDelegate
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Hero(
|
return Hero(
|
||||||
tag: heroTag,
|
tag: heroTag == _defaultHeroTag
|
||||||
|
? _HeroTag(Navigator.of(context))
|
||||||
|
: heroTag,
|
||||||
createRectTween: _linearTranslateWithLargestRectSizeTween,
|
createRectTween: _linearTranslateWithLargestRectSizeTween,
|
||||||
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
|
flightShuttleBuilder: _navBarHeroFlightShuttleBuilder,
|
||||||
placeholderBuilder: _navBarHeroLaunchPadBuilder,
|
placeholderBuilder: _navBarHeroLaunchPadBuilder,
|
||||||
|
@ -222,10 +222,6 @@ class Hero extends StatefulWidget {
|
|||||||
result[tag] = heroState;
|
result[tag] = heroState;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Don't perform transitions across different Navigators.
|
|
||||||
if (element.widget is Navigator) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
element.visitChildren(visitor);
|
element.visitChildren(visitor);
|
||||||
}
|
}
|
||||||
context.visitChildElements(visitor);
|
context.visitChildElements(visitor);
|
||||||
|
@ -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',
|
testWidgets('Transition box grows to large title size',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
await startTransitionBetween(
|
await startTransitionBetween(
|
||||||
|
Loading…
Reference in New Issue
Block a user