Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
db4c98fc
Unverified
Commit
db4c98fc
authored
Feb 15, 2022
by
Hans Muller
Committed by
GitHub
Feb 15, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added a NavgationBar example with nested Navigators (#98440)
parent
4ee22ed2
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
493 additions
and
0 deletions
+493
-0
navigation_bar.0.dart
...les/api/lib/material/navigation_bar/navigation_bar.0.dart
+370
-0
navigation_bar.0.test.dart
...i/test/material/navigation_bar/navigation_bar.0.test.dart
+108
-0
navigation_bar.dart
packages/flutter/lib/src/material/navigation_bar.dart
+15
-0
No files found.
examples/api/lib/material/navigation_bar/navigation_bar.0.dart
0 → 100644
View file @
db4c98fc
// 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
({
Key
?
key
,
required
this
.
destination
})
:
super
(
key:
key
);
final
Destination
destination
;
Widget
_buildDialog
(
BuildContext
context
)
{
return
AlertDialog
(
title:
Text
(
'
${destination.title}
AlertDialog'
),
actions:
<
Widget
>[
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:
<
Widget
>[
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
({
Key
?
key
,
required
this
.
destination
})
:
super
(
key:
key
);
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:
MaterialStateProperty
.
all
<
Color
>(
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
({
Key
?
key
,
required
this
.
destination
})
:
super
(
key:
key
);
final
Destination
destination
;
@override
State
<
TextPage
>
createState
()
=>
_TextPageState
();
}
class
_TextPageState
extends
State
<
TextPage
>
{
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
({
Key
?
key
,
required
this
.
destination
,
required
this
.
navigatorKey
,
})
:
super
(
key:
key
);
final
Destination
destination
;
final
Key
navigatorKey
;
@override
State
<
DestinationView
>
createState
()
=>
_DestinationViewState
();
}
class
_DestinationViewState
extends
State
<
DestinationView
>
{
@override
Widget
build
(
BuildContext
context
)
{
return
Navigator
(
key:
widget
.
navigatorKey
,
onGenerateRoute:
(
RouteSettings
settings
)
{
return
MaterialPageRoute
<
void
>(
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
({
Key
?
key
})
:
super
(
key:
key
);
@override
State
<
Home
>
createState
()
=>
_HomeState
();
}
class
_HomeState
extends
State
<
Home
>
with
TickerProviderStateMixin
<
Home
>
{
static
const
List
<
Destination
>
allDestinations
=
<
Destination
>[
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
<
GlobalKey
<
NavigatorState
>>
navigatorKeys
;
late
final
List
<
GlobalKey
>
destinationKeys
;
late
final
List
<
AnimationController
>
destinationFaders
;
late
final
List
<
Widget
>
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
<
GlobalKey
<
NavigatorState
>>.
generate
(
allDestinations
.
length
,
(
int
index
)
=>
GlobalKey
()).
toList
();
destinationFaders
=
List
<
AnimationController
>.
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
()));
}
examples/api/test/material/navigation_bar/navigation_bar.0.test.dart
0 → 100644
View file @
db4c98fc
// 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.
import
'package:flutter/material.dart'
;
import
'package:flutter_api_samples/material/navigation_bar/navigation_bar.0.dart'
as
example
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'RootPage: only selected destination is on stage'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
example
.
Home
()));
const
String
tealTitle
=
'Teal RootPage - /'
;
const
String
cyanTitle
=
'Cyan RootPage - /'
;
const
String
orangeTitle
=
'Orange RootPage - /'
;
const
String
blueTitle
=
'Blue RootPage - /'
;
await
tester
.
tap
(
find
.
widgetWithText
(
NavigationDestination
,
'Teal'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tealTitle
),
findsOneWidget
);
expect
(
find
.
text
(
cyanTitle
),
findsNothing
);
expect
(
find
.
text
(
orangeTitle
),
findsNothing
);
expect
(
find
.
text
(
blueTitle
),
findsNothing
);
await
tester
.
tap
(
find
.
widgetWithText
(
NavigationDestination
,
'Cyan'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tealTitle
),
findsNothing
);
expect
(
find
.
text
(
cyanTitle
),
findsOneWidget
);
expect
(
find
.
text
(
orangeTitle
),
findsNothing
);
expect
(
find
.
text
(
blueTitle
),
findsNothing
);
await
tester
.
tap
(
find
.
widgetWithText
(
NavigationDestination
,
'Orange'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tealTitle
),
findsNothing
);
expect
(
find
.
text
(
cyanTitle
),
findsNothing
);
expect
(
find
.
text
(
orangeTitle
),
findsOneWidget
);
expect
(
find
.
text
(
blueTitle
),
findsNothing
);
await
tester
.
tap
(
find
.
widgetWithText
(
NavigationDestination
,
'Blue'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
tealTitle
),
findsNothing
);
expect
(
find
.
text
(
cyanTitle
),
findsNothing
);
expect
(
find
.
text
(
orangeTitle
),
findsNothing
);
expect
(
find
.
text
(
blueTitle
),
findsOneWidget
);
});
testWidgets
(
'RootPage'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
example
.
Home
()));
await
tester
.
tap
(
find
.
widgetWithText
(
NavigationDestination
,
'Teal'
));
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
text
(
'Local Dialog'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal AlertDialog'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'OK'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal AlertDialog'
),
findsNothing
);
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
text
(
'Root Dialog'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal AlertDialog'
),
findsOneWidget
);
await
tester
.
tapAt
(
const
Offset
(
5
,
5
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal AlertDialog'
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'Local BottomSheet'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
BottomSheet
),
findsOneWidget
);
await
tester
.
tap
(
find
.
byType
(
BackButton
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
byType
(
BottomSheet
),
findsNothing
);
await
tester
.
tap
(
find
.
text
(
'Push /list'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal ListPage - /list'
),
findsOneWidget
);
});
testWidgets
(
'ListPage'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
const
MaterialApp
(
home:
example
.
Home
()));
expect
(
find
.
text
(
'Teal RootPage - /'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
widgetWithText
(
ElevatedButton
,
'Push /list'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal ListPage - /list'
),
findsOneWidget
);
expect
(
find
.
text
(
'Push /text [0]'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
widgetWithText
(
NavigationDestination
,
'Orange'
));
await
tester
.
pumpAndSettle
();
await
tester
.
tap
(
find
.
widgetWithText
(
ElevatedButton
,
'Push /list'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Orange ListPage - /list'
),
findsOneWidget
);
expect
(
find
.
text
(
'Push /text [0]'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
byType
(
BackButton
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Orange RootPage - /'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
widgetWithText
(
NavigationDestination
,
'Teal'
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal ListPage - /list'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
byType
(
BackButton
));
await
tester
.
pumpAndSettle
();
expect
(
find
.
text
(
'Teal RootPage - /'
),
findsOneWidget
);
});
}
packages/flutter/lib/src/material/navigation_bar.dart
View file @
db4c98fc
...
...
@@ -59,6 +59,21 @@ import 'tooltip.dart';
/// ),
/// ),
/// ```
///
/// {@tool dartpad}
/// This example has a [NavigationBar] where each destination has its
/// own Navigator, Scaffold, and Appbar. That means that each
/// destination has an independent route history and (app bar) back
/// button. A [Stack] is used to display one destination at a time and
/// destination changes are handled by cross fade transitions. Destinations
/// that have been completely faded out are [Offstage].
///
/// One can see that the appearance of each destination's dialogs, bottom sheet,
/// list scrolling state, and text field state, persist when another destination
/// is selected.
///
/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.0.dart **
/// {@end-tool}
class
NavigationBar
extends
StatelessWidget
{
/// Creates a Material 3 Navigation Bar component.
///
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment