3
3
// found in the LICENSE file.
4
4
5
5
import 'package:collection/collection.dart' ;
6
+ import 'package:flutter/cupertino.dart' ;
6
7
import 'package:flutter/material.dart' ;
7
8
import 'package:go_router/go_router.dart' ;
8
9
9
10
final GlobalKey <NavigatorState > _rootNavigatorKey =
10
11
GlobalKey <NavigatorState >(debugLabel: 'root' );
11
12
final GlobalKey <NavigatorState > _tabANavigatorKey =
12
13
GlobalKey <NavigatorState >(debugLabel: 'tabANav' );
14
+ @visibleForTesting
15
+ // ignore: public_member_api_docs
16
+ final GlobalKey <TabbedRootScreenState > tabbedRootScreenKey =
17
+ GlobalKey <TabbedRootScreenState >(debugLabel: 'TabbedRootScreen' );
13
18
14
19
// This example demonstrates how to setup nested navigation using a
15
20
// BottomNavigationBar, where each bar item uses its own persistent navigator,
@@ -52,6 +57,8 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
52
57
// are managed (using AnimatedBranchContainer).
53
58
return ScaffoldWithNavBar (
54
59
navigationShell: navigationShell, children: children);
60
+ // NOTE: To use a Cupertino version of ScaffoldWithNavBar, replace
61
+ // ScaffoldWithNavBar above with CupertinoScaffoldWithNavBar.
55
62
},
56
63
branches: < StatefulShellBranch > [
57
64
// The route branch for the first tab of the bottom navigation bar.
@@ -78,13 +85,13 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
78
85
],
79
86
),
80
87
81
- // The route branch for the third tab of the bottom navigation bar.
88
+ // The route branch for the second tab of the bottom navigation bar.
82
89
StatefulShellBranch (
83
90
// StatefulShellBranch will automatically use the first descendant
84
91
// GoRoute as the initial location of the branch. If another route
85
92
// is desired, specify the location of it using the defaultLocation
86
93
// parameter.
87
- // defaultLocation: '/c2 ',
94
+ // defaultLocation: '/b2 ',
88
95
routes: < RouteBase > [
89
96
StatefulShellRoute (
90
97
builder: (BuildContext context, GoRouterState state,
@@ -102,7 +109,12 @@ class NestedTabNavigationExampleApp extends StatelessWidget {
102
109
// See TabbedRootScreen for more details on how the children
103
110
// are managed (in a TabBarView).
104
111
return TabbedRootScreen (
105
- navigationShell: navigationShell, children: children);
112
+ navigationShell: navigationShell,
113
+ key: tabbedRootScreenKey,
114
+ children: children,
115
+ );
116
+ // NOTE: To use a PageView version of TabbedRootScreen,
117
+ // replace TabbedRootScreen above with PagedRootScreen.
106
118
},
107
119
// This bottom tab uses a nested shell, wrapping sub routes in a
108
120
// top TabBar.
@@ -222,6 +234,70 @@ class ScaffoldWithNavBar extends StatelessWidget {
222
234
}
223
235
}
224
236
237
+ /// Alternative version of [ScaffoldWithNavBar] , using a [CupertinoTabScaffold] .
238
+ // ignore: unused_element, unreachable_from_main
239
+ class CupertinoScaffoldWithNavBar extends StatefulWidget {
240
+ /// Constructs an [ScaffoldWithNavBar] .
241
+ // ignore: unreachable_from_main
242
+ const CupertinoScaffoldWithNavBar ({
243
+ required this .navigationShell,
244
+ required this .children,
245
+ Key ? key,
246
+ }) : super (key: key ?? const ValueKey <String >('ScaffoldWithNavBar' ));
247
+
248
+ /// The navigation shell and container for the branch Navigators.
249
+ // ignore: unreachable_from_main
250
+ final StatefulNavigationShell navigationShell;
251
+
252
+ /// The children (branch Navigators) to display in a custom container
253
+ /// ([AnimatedBranchContainer] ).
254
+ // ignore: unreachable_from_main
255
+ final List <Widget > children;
256
+
257
+ @override
258
+ State <StatefulWidget > createState () => _CupertinoScaffoldWithNavBarState ();
259
+ }
260
+
261
+ class _CupertinoScaffoldWithNavBarState
262
+ extends State <CupertinoScaffoldWithNavBar > {
263
+ late final CupertinoTabController tabController =
264
+ CupertinoTabController (initialIndex: widget.navigationShell.currentIndex);
265
+
266
+ @override
267
+ void dispose () {
268
+ tabController.dispose ();
269
+ super .dispose ();
270
+ }
271
+
272
+ @override
273
+ Widget build (BuildContext context) {
274
+ return CupertinoTabScaffold (
275
+ controller: tabController,
276
+ tabBar: CupertinoTabBar (
277
+ items: const < BottomNavigationBarItem > [
278
+ BottomNavigationBarItem (icon: Icon (Icons .home), label: 'Section A' ),
279
+ BottomNavigationBarItem (icon: Icon (Icons .work), label: 'Section B' ),
280
+ ],
281
+ currentIndex: widget.navigationShell.currentIndex,
282
+ onTap: (int index) => _onTap (context, index),
283
+ ),
284
+ // Note: It is common to use CupertinoTabView for the tabBuilder when
285
+ // using CupertinoTabScaffold and CupertinoTabBar. This would however be
286
+ // redundant when using StatefulShellRoute, since a separate Navigator is
287
+ // already created for each branch, meaning we can simply use the branch
288
+ // Navigator Widgets (i.e. widget.children) directly.
289
+ tabBuilder: (BuildContext context, int index) => widget.children[index],
290
+ );
291
+ }
292
+
293
+ void _onTap (BuildContext context, int index) {
294
+ widget.navigationShell.goBranch (
295
+ index,
296
+ initialLocation: index == widget.navigationShell.currentIndex,
297
+ );
298
+ }
299
+ }
300
+
225
301
/// Custom branch Navigator container that provides animated transitions
226
302
/// when switching branches.
227
303
class AnimatedBranchContainer extends StatelessWidget {
@@ -271,7 +347,7 @@ class RootScreenA extends StatelessWidget {
271
347
Widget build (BuildContext context) {
272
348
return Scaffold (
273
349
appBar: AppBar (
274
- title: const Text ('Root of section A ' ),
350
+ title: const Text ('Section A root ' ),
275
351
),
276
352
body: Center (
277
353
child: Column (
@@ -386,20 +462,43 @@ class TabbedRootScreen extends StatefulWidget {
386
462
final List <Widget > children;
387
463
388
464
@override
389
- State <StatefulWidget > createState () => _TabbedRootScreenState ();
465
+ State <StatefulWidget > createState () => TabbedRootScreenState ();
390
466
}
391
467
392
- class _TabbedRootScreenState extends State <TabbedRootScreen >
468
+ @visibleForTesting
469
+ // ignore: public_member_api_docs
470
+ class TabbedRootScreenState extends State <TabbedRootScreen >
393
471
with SingleTickerProviderStateMixin {
394
- late final TabController _tabController = TabController (
472
+ @visibleForTesting
473
+ // ignore: public_member_api_docs
474
+ late final TabController tabController = TabController (
395
475
length: widget.children.length,
396
476
vsync: this ,
397
477
initialIndex: widget.navigationShell.currentIndex);
398
478
479
+ void _switchedTab () {
480
+ if (tabController.index != widget.navigationShell.currentIndex) {
481
+ widget.navigationShell.goBranch (tabController.index);
482
+ }
483
+ }
484
+
485
+ @override
486
+ void initState () {
487
+ super .initState ();
488
+ tabController.addListener (_switchedTab);
489
+ }
490
+
491
+ @override
492
+ void dispose () {
493
+ tabController.removeListener (_switchedTab);
494
+ tabController.dispose ();
495
+ super .dispose ();
496
+ }
497
+
399
498
@override
400
499
void didUpdateWidget (covariant TabbedRootScreen oldWidget) {
401
500
super .didUpdateWidget (oldWidget);
402
- _tabController .index = widget.navigationShell.currentIndex;
501
+ tabController .index = widget.navigationShell.currentIndex;
403
502
}
404
503
405
504
@override
@@ -410,14 +509,15 @@ class _TabbedRootScreenState extends State<TabbedRootScreen>
410
509
411
510
return Scaffold (
412
511
appBar: AppBar (
413
- title: const Text ('Root of Section B (nested TabBar shell)' ),
512
+ title: Text (
513
+ 'Section B root (tab: ${widget .navigationShell .currentIndex + 1 })' ),
414
514
bottom: TabBar (
415
- controller: _tabController ,
515
+ controller: tabController ,
416
516
tabs: tabs,
417
517
onTap: (int tappedIndex) => _onTabTap (context, tappedIndex),
418
518
)),
419
519
body: TabBarView (
420
- controller: _tabController ,
520
+ controller: tabController ,
421
521
children: widget.children,
422
522
),
423
523
);
@@ -428,6 +528,84 @@ class _TabbedRootScreenState extends State<TabbedRootScreen>
428
528
}
429
529
}
430
530
531
+ /// Alternative implementation of TabbedRootScreen, demonstrating the use of
532
+ /// a [PageView] .
533
+ // ignore: unreachable_from_main
534
+ class PagedRootScreen extends StatefulWidget {
535
+ /// Constructs a PagedRootScreen
536
+ // ignore: unreachable_from_main
537
+ const PagedRootScreen (
538
+ {required this .navigationShell, required this .children, super .key});
539
+
540
+ /// The current state of the parent StatefulShellRoute.
541
+ // ignore: unreachable_from_main
542
+ final StatefulNavigationShell navigationShell;
543
+
544
+ /// The children (branch Navigators) to display in the [TabBarView] .
545
+ // ignore: unreachable_from_main
546
+ final List <Widget > children;
547
+
548
+ @override
549
+ State <StatefulWidget > createState () => _PagedRootScreenState ();
550
+ }
551
+
552
+ /// Alternative implementation _TabbedRootScreenState, demonstrating the use of
553
+ /// a PageView.
554
+ class _PagedRootScreenState extends State <PagedRootScreen > {
555
+ late final PageController _pageController = PageController (
556
+ initialPage: widget.navigationShell.currentIndex,
557
+ );
558
+
559
+ @override
560
+ void dispose () {
561
+ _pageController.dispose ();
562
+ super .dispose ();
563
+ }
564
+
565
+ @override
566
+ Widget build (BuildContext context) {
567
+ return Scaffold (
568
+ appBar: AppBar (
569
+ title: Text (
570
+ 'Section B root (tab ${widget .navigationShell .currentIndex + 1 })' ),
571
+ ),
572
+ body: Column (
573
+ children: < Widget > [
574
+ Row (
575
+ mainAxisAlignment: MainAxisAlignment .spaceEvenly,
576
+ children: < Widget > [
577
+ ElevatedButton (
578
+ onPressed: () => _animateToPage (0 ),
579
+ child: const Text ('Tab 1' ),
580
+ ),
581
+ ElevatedButton (
582
+ onPressed: () => _animateToPage (1 ),
583
+ child: const Text ('Tab 2' ),
584
+ ),
585
+ ]),
586
+ Expanded (
587
+ child: PageView (
588
+ onPageChanged: (int i) => widget.navigationShell.goBranch (i),
589
+ controller: _pageController,
590
+ children: widget.children,
591
+ ),
592
+ ),
593
+ ],
594
+ ),
595
+ );
596
+ }
597
+
598
+ void _animateToPage (int index) {
599
+ if (_pageController.hasClients) {
600
+ _pageController.animateToPage (
601
+ index,
602
+ duration: const Duration (milliseconds: 500 ),
603
+ curve: Curves .bounceOut,
604
+ );
605
+ }
606
+ }
607
+ }
608
+
431
609
/// Widget for the pages in the top tab bar.
432
610
class TabScreen extends StatelessWidget {
433
611
/// Creates a RootScreen
0 commit comments