// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Flutter code sample for NavigationBar with nested Navigator destinations. import 'package:flutter/material.dart'; class Destination { const Destination(this.index, this.title, this.icon, this.color); final int index; final String title; final IconData icon; final MaterialColor color; } class RootPage extends StatelessWidget { const RootPage({ super.key, required this.destination }); final Destination destination; Widget _buildDialog(BuildContext context) { return AlertDialog( title: Text('${destination.title} AlertDialog'), actions: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('OK'), ), ], ); } @override Widget build(BuildContext context) { final TextStyle headline5 = Theme.of(context).textTheme.headline5!; final ButtonStyle buttonStyle = ElevatedButton.styleFrom( primary: destination.color, visualDensity: VisualDensity.comfortable, padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), textStyle: headline5, ); return Scaffold( appBar: AppBar( title: Text('${destination.title} RootPage - /'), backgroundColor: destination.color, ), backgroundColor: destination.color[50], body: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ ElevatedButton( style: buttonStyle, onPressed: () { Navigator.pushNamed(context, '/list'); }, child: const Text('Push /list'), ), const SizedBox(height: 16), ElevatedButton( style: buttonStyle, onPressed: () { showDialog( context: context, useRootNavigator: false, builder: _buildDialog, ); }, child: const Text('Local Dialog'), ), const SizedBox(height: 16), ElevatedButton( style: buttonStyle, onPressed: () { showDialog( context: context, useRootNavigator: true, builder: _buildDialog, ); }, child: const Text('Root Dialog'), ), const SizedBox(height: 16), Builder( builder: (BuildContext context) { return ElevatedButton( style: buttonStyle, onPressed: () { showBottomSheet( context: context, builder: (BuildContext context) { return Container( padding: const EdgeInsets.all(16), width: double.infinity, child: Text( '${destination.title} BottomSheet\n' 'Tap the back button to dismiss', style: headline5, softWrap: true, textAlign: TextAlign.center, ), ); }, ); }, child: const Text('Local BottomSheet'), ); }, ), ], ), ), ); } } class ListPage extends StatelessWidget { const ListPage({ super.key, required this.destination }); final Destination destination; @override Widget build(BuildContext context) { const int itemCount = 50; final ButtonStyle buttonStyle = OutlinedButton.styleFrom( primary: destination.color, fixedSize: const Size.fromHeight(128), textStyle: Theme.of(context).textTheme.headline5, ); return Scaffold( appBar: AppBar( title: Text('${destination.title} ListPage - /list'), backgroundColor: destination.color, ), backgroundColor: destination.color[50], body: SizedBox.expand( child: ListView.builder( itemCount: itemCount, itemBuilder: (BuildContext context, int index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), child: OutlinedButton( style: buttonStyle.copyWith( backgroundColor: MaterialStatePropertyAll( Color.lerp(destination.color[100], Colors.white, index / itemCount)! ), ), onPressed: () { Navigator.pushNamed(context, '/text'); }, child: Text('Push /text [$index]'), ), ); }, ), ), ); } } class TextPage extends StatefulWidget { const TextPage({ super.key, required this.destination }); final Destination destination; @override State createState() => _TextPageState(); } class _TextPageState extends State { late final TextEditingController textController; @override void initState() { super.initState(); textController = TextEditingController(text: 'Sample Text'); } @override void dispose() { textController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final ThemeData theme = Theme.of(context); return Scaffold( appBar: AppBar( title: Text('${widget.destination.title} TextPage - /list/text'), backgroundColor: widget.destination.color, ), backgroundColor: widget.destination.color[50], body: Container( padding: const EdgeInsets.all(32.0), alignment: Alignment.center, child: TextField( controller: textController, style: theme.primaryTextTheme.headline4?.copyWith( color: widget.destination.color, ), decoration: InputDecoration( focusedBorder: UnderlineInputBorder( borderSide: BorderSide( color: widget.destination.color, width: 3.0, ), ), ), ), ), ); } } class DestinationView extends StatefulWidget { const DestinationView({ super.key, required this.destination, required this.navigatorKey, }); final Destination destination; final Key navigatorKey; @override State createState() => _DestinationViewState(); } class _DestinationViewState extends State { @override Widget build(BuildContext context) { return Navigator( key: widget.navigatorKey, onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute( settings: settings, builder: (BuildContext context) { switch(settings.name) { case '/': return RootPage(destination: widget.destination); case '/list': return ListPage(destination: widget.destination); case '/text': return TextPage(destination: widget.destination); } assert(false); return const SizedBox(); }, ); }, ); } } class Home extends StatefulWidget { const Home({ super.key }); @override State createState() => _HomeState(); } class _HomeState extends State with TickerProviderStateMixin { static const List allDestinations = [ Destination(0, 'Teal', Icons.home, Colors.teal), Destination(1, 'Cyan', Icons.business, Colors.cyan), Destination(2, 'Orange', Icons.school, Colors.orange), Destination(3, 'Blue', Icons.flight, Colors.blue), ]; late final List> navigatorKeys; late final List destinationKeys; late final List destinationFaders; late final List destinationViews; int selectedIndex = 0; AnimationController buildFaderController() { final AnimationController controller = AnimationController(vsync: this, duration: const Duration(milliseconds: 200)); controller.addStatusListener((AnimationStatus status) { if (status == AnimationStatus.dismissed) { setState(() { }); // Rebuild unselected destinations offstage. } }); return controller; } @override void initState() { super.initState(); navigatorKeys = List>.generate(allDestinations.length, (int index) => GlobalKey()).toList(); destinationFaders = List.generate(allDestinations.length, (int index) => buildFaderController()).toList(); destinationFaders[selectedIndex].value = 1.0; destinationViews = allDestinations.map((Destination destination) { return FadeTransition( opacity: destinationFaders[destination.index].drive(CurveTween(curve: Curves.fastOutSlowIn)), child: KeyedSubtree( key: GlobalKey(), child: DestinationView( destination: destination, navigatorKey: navigatorKeys[destination.index], ), ) ); }).toList(); } @override void dispose() { for (final AnimationController controller in destinationFaders) { controller.dispose(); } super.dispose(); } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { final NavigatorState navigator = navigatorKeys[selectedIndex].currentState!; if (!navigator.canPop()) { return true; } navigator.pop(); return false; }, child: Scaffold( body: SafeArea( top: false, child: Stack( fit: StackFit.expand, children: allDestinations.map((Destination destination) { final int index = destination.index; final Widget view = destinationViews[index]; if (index == selectedIndex) { destinationFaders[index].forward(); return Offstage(offstage: false, child: view); } else { destinationFaders[index].reverse(); if (destinationFaders[index].isAnimating) { return IgnorePointer(child: view); } return Offstage(child: view); } }).toList(), ), ), bottomNavigationBar: NavigationBar( selectedIndex: selectedIndex, onDestinationSelected: (int index) { setState(() { selectedIndex = index; }); }, destinations: allDestinations.map((Destination destination) { return NavigationDestination( icon: Icon(destination.icon, color: destination.color), label: destination.title, ); }).toList(), ), ), ); } } void main() { runApp(const MaterialApp(home: Home())); }