NavigationBar
improvements (#116992)
This commit is contained in:
parent
f0ea376460
commit
f07db4018a
@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() => runApp(const ExampleApp());
|
void main() => runApp(const NavigationBarApp());
|
||||||
|
|
||||||
class ExampleApp extends StatelessWidget {
|
class NavigationBarApp extends StatelessWidget {
|
||||||
const ExampleApp({super.key});
|
const NavigationBarApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -2,223 +2,93 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
/// Flutter code sample for [NavigationBar] with nested [Navigator] destinations.
|
/// Flutter code sample for [NavigationBar].
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() {
|
void main() => runApp(const NavigationBarApp());
|
||||||
runApp(const MaterialApp(home: Home()));
|
|
||||||
}
|
|
||||||
|
|
||||||
class Home extends StatefulWidget {
|
class NavigationBarApp extends StatelessWidget {
|
||||||
const Home({ super.key });
|
const NavigationBarApp({super.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: DestinationView(
|
|
||||||
destination: destination,
|
|
||||||
navigatorKey: navigatorKeys[destination.index],
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}).toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
for (final AnimationController controller in destinationFaders) {
|
|
||||||
controller.dispose();
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return WillPopScope(
|
return const MaterialApp(home: NavigationExample());
|
||||||
onWillPop: () async {
|
|
||||||
final NavigatorState navigator = navigatorKeys[selectedIndex].currentState!;
|
|
||||||
if (!navigator.canPop()) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
navigator.pop();
|
}
|
||||||
return false;
|
|
||||||
},
|
class NavigationExample extends StatefulWidget {
|
||||||
child: Scaffold(
|
const NavigationExample({super.key});
|
||||||
body: SafeArea(
|
|
||||||
top: false,
|
@override
|
||||||
child: Stack(
|
State<NavigationExample> createState() => _NavigationExampleState();
|
||||||
fit: StackFit.expand,
|
}
|
||||||
children: allDestinations.map((Destination destination) {
|
|
||||||
final int index = destination.index;
|
class _NavigationExampleState extends State<NavigationExample> {
|
||||||
final Widget view = destinationViews[index];
|
int currentPageIndex = 0;
|
||||||
if (index == selectedIndex) {
|
NavigationDestinationLabelBehavior labelBehavior = NavigationDestinationLabelBehavior.alwaysShow;
|
||||||
destinationFaders[index].forward();
|
|
||||||
return Offstage(offstage: false, child: view);
|
@override
|
||||||
} else {
|
Widget build(BuildContext context) {
|
||||||
destinationFaders[index].reverse();
|
return Scaffold(
|
||||||
if (destinationFaders[index].isAnimating) {
|
|
||||||
return IgnorePointer(child: view);
|
|
||||||
}
|
|
||||||
return Offstage(child: view);
|
|
||||||
}
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
bottomNavigationBar: NavigationBar(
|
bottomNavigationBar: NavigationBar(
|
||||||
selectedIndex: selectedIndex,
|
labelBehavior: labelBehavior,
|
||||||
|
selectedIndex: currentPageIndex,
|
||||||
onDestinationSelected: (int index) {
|
onDestinationSelected: (int index) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedIndex = index;
|
currentPageIndex = index;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
destinations: allDestinations.map((Destination destination) {
|
destinations: const <Widget>[
|
||||||
return NavigationDestination(
|
NavigationDestination(
|
||||||
icon: Icon(destination.icon, color: destination.color),
|
icon: Icon(Icons.explore),
|
||||||
label: destination.title,
|
label: 'Explore',
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
),
|
||||||
|
NavigationDestination(
|
||||||
|
icon: Icon(Icons.commute),
|
||||||
|
label: 'Commute',
|
||||||
),
|
),
|
||||||
);
|
NavigationDestination(
|
||||||
}
|
selectedIcon: Icon(Icons.bookmark),
|
||||||
}
|
icon: Icon(Icons.bookmark_border),
|
||||||
|
label: 'Saved',
|
||||||
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: <Widget>[
|
|
||||||
TextButton(
|
|
||||||
onPressed: () { Navigator.pop(context); },
|
|
||||||
child: const Text('OK'),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final TextStyle headlineSmall = Theme.of(context).textTheme.headlineSmall!;
|
|
||||||
final ButtonStyle buttonStyle = ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: destination.color,
|
|
||||||
visualDensity: VisualDensity.comfortable,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
|
||||||
textStyle: headlineSmall,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text('${destination.title} RootPage - /'),
|
|
||||||
backgroundColor: destination.color,
|
|
||||||
),
|
),
|
||||||
backgroundColor: destination.color[50],
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Text('Label behavior: ${labelBehavior.name}'),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
OverflowBar(
|
||||||
|
spacing: 10.0,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: buttonStyle,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pushNamed(context, '/list');
|
setState(() {
|
||||||
|
labelBehavior = NavigationDestinationLabelBehavior.alwaysShow;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
child: const Text('Push /list'),
|
child: const Text('alwaysShow'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: buttonStyle,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
setState(() {
|
||||||
context: context,
|
labelBehavior = NavigationDestinationLabelBehavior.onlyShowSelected;
|
||||||
useRootNavigator: false,
|
});
|
||||||
builder: _buildDialog,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: const Text('Local Dialog'),
|
child: const Text('onlyShowSelected'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: buttonStyle,
|
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showDialog(
|
setState(() {
|
||||||
context: context,
|
labelBehavior = NavigationDestinationLabelBehavior.alwaysHide;
|
||||||
useRootNavigator: true,
|
});
|
||||||
builder: _buildDialog,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: const Text('Root Dialog'),
|
child: const Text('alwaysHide'),
|
||||||
),
|
),
|
||||||
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: headlineSmall,
|
|
||||||
softWrap: true,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: const Text('Local BottomSheet'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@ -226,142 +96,3 @@ class RootPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
|
||||||
foregroundColor: destination.color,
|
|
||||||
fixedSize: const Size.fromHeight(128),
|
|
||||||
textStyle: Theme.of(context).textTheme.headlineSmall,
|
|
||||||
);
|
|
||||||
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>(
|
|
||||||
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<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.headlineMedium?.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<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();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
367
examples/api/lib/material/navigation_bar/navigation_bar.2.dart
Normal file
367
examples/api/lib/material/navigation_bar/navigation_bar.2.dart
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
// 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';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
runApp(const MaterialApp(home: Home()));
|
||||||
|
}
|
||||||
|
|
||||||
|
class Home extends StatefulWidget {
|
||||||
|
const Home({ super.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: 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(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () { Navigator.pop(context); },
|
||||||
|
child: const Text('OK'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final TextStyle headlineSmall = Theme.of(context).textTheme.headlineSmall!;
|
||||||
|
final ButtonStyle buttonStyle = ElevatedButton.styleFrom(
|
||||||
|
backgroundColor: destination.color,
|
||||||
|
visualDensity: VisualDensity.comfortable,
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||||
|
textStyle: headlineSmall,
|
||||||
|
);
|
||||||
|
|
||||||
|
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: headlineSmall,
|
||||||
|
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(
|
||||||
|
foregroundColor: destination.color,
|
||||||
|
fixedSize: const Size.fromHeight(128),
|
||||||
|
textStyle: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
);
|
||||||
|
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>(
|
||||||
|
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<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.headlineMedium?.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<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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,10 +11,9 @@ void main() {
|
|||||||
testWidgets('Navigation bar updates destination on tap',
|
testWidgets('Navigation bar updates destination on tap',
|
||||||
(WidgetTester tester) async {
|
(WidgetTester tester) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
const example.ExampleApp(),
|
const example.NavigationBarApp(),
|
||||||
);
|
);
|
||||||
final NavigationBar navigationBarWidget =
|
final NavigationBar navigationBarWidget = tester.firstWidget(find.byType(NavigationBar));
|
||||||
tester.firstWidget(find.byType(NavigationBar));
|
|
||||||
|
|
||||||
/// NavigationDestinations must be rendered
|
/// NavigationDestinations must be rendered
|
||||||
expect(find.text('Explore'), findsOneWidget);
|
expect(find.text('Explore'), findsOneWidget);
|
||||||
|
@ -3,106 +3,41 @@
|
|||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_api_samples/material/navigation_bar/navigation_bar.1.dart' as example;
|
import 'package:flutter_api_samples/material/navigation_bar/navigation_bar.1.dart'
|
||||||
|
as example;
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets('RootPage: only selected destination is on stage', (WidgetTester tester) async {
|
testWidgets('Navigation bar updates label behavior when tapping buttons',
|
||||||
await tester.pumpWidget(const MaterialApp(home: example.Home()));
|
(WidgetTester tester) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
const example.NavigationBarApp(),
|
||||||
|
);
|
||||||
|
NavigationBar navigationBarWidget = tester.firstWidget(find.byType(NavigationBar));
|
||||||
|
|
||||||
const String tealTitle = 'Teal RootPage - /';
|
expect(find.text('Label behavior: alwaysShow'), findsOneWidget);
|
||||||
const String cyanTitle = 'Cyan RootPage - /';
|
|
||||||
const String orangeTitle = 'Orange RootPage - /';
|
|
||||||
const String blueTitle = 'Blue RootPage - /';
|
|
||||||
|
|
||||||
await tester.tap(find.widgetWithText(NavigationDestination, 'Teal'));
|
/// Test alwaysShow label behavior button.
|
||||||
|
await tester.tap(find.widgetWithText(ElevatedButton, 'alwaysShow'));
|
||||||
await tester.pumpAndSettle();
|
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'));
|
expect(find.text('Label behavior: alwaysShow'), findsOneWidget);
|
||||||
await tester.pumpAndSettle();
|
expect(navigationBarWidget.labelBehavior, NavigationDestinationLabelBehavior.alwaysShow);
|
||||||
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'));
|
/// Test onlyShowSelected label behavior button.
|
||||||
|
await tester.tap(find.widgetWithText(ElevatedButton, 'onlyShowSelected'));
|
||||||
await tester.pumpAndSettle();
|
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'));
|
expect(find.text('Label behavior: onlyShowSelected'), findsOneWidget);
|
||||||
await tester.pumpAndSettle();
|
navigationBarWidget = tester.firstWidget(find.byType(NavigationBar));
|
||||||
expect(find.text(tealTitle), findsNothing);
|
expect(navigationBarWidget.labelBehavior, NavigationDestinationLabelBehavior.onlyShowSelected);
|
||||||
expect(find.text(cyanTitle), findsNothing);
|
|
||||||
expect(find.text(orangeTitle), findsNothing);
|
|
||||||
expect(find.text(blueTitle), findsOneWidget);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('RootPage', (WidgetTester tester) async {
|
/// Test alwaysHide label behavior button.
|
||||||
await tester.pumpWidget(const MaterialApp(home: example.Home()));
|
await tester.tap(find.widgetWithText(ElevatedButton, 'alwaysHide'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
await tester.tap(find.widgetWithText(NavigationDestination, 'Teal'));
|
expect(find.text('Label behavior: alwaysHide'), findsOneWidget);
|
||||||
await tester.pumpAndSettle();
|
navigationBarWidget = tester.firstWidget(find.byType(NavigationBar));
|
||||||
await tester.tap(find.text('Local Dialog'));
|
expect(navigationBarWidget.labelBehavior, NavigationDestinationLabelBehavior.alwaysHide);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,108 @@
|
|||||||
|
// 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.2.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);
|
||||||
|
});
|
||||||
|
}
|
@ -53,6 +53,14 @@ const double _kIndicatorWidth = 64;
|
|||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
///
|
///
|
||||||
/// {@tool dartpad}
|
/// {@tool dartpad}
|
||||||
|
/// This example showcases [NavigationBar] label behaviors. When tapping on one
|
||||||
|
/// of the label behavior options, the [labelBehavior] of the [NavigationBar]
|
||||||
|
/// will be updated.
|
||||||
|
///
|
||||||
|
/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.1.dart **
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// {@tool dartpad}
|
||||||
/// This example shows a [NavigationBar] as it is used within a [Scaffold]
|
/// This example shows a [NavigationBar] as it is used within a [Scaffold]
|
||||||
/// widget when there are nested navigators that provide local navigation. The
|
/// widget when there are nested navigators that provide local navigation. The
|
||||||
/// [NavigationBar] has four [NavigationDestination] widgets with different
|
/// [NavigationBar] has four [NavigationDestination] widgets with different
|
||||||
@ -60,7 +68,7 @@ const double _kIndicatorWidth = 64;
|
|||||||
/// item's index and displays a corresponding page with its own local navigator
|
/// item's index and displays a corresponding page with its own local navigator
|
||||||
/// in the body of a [Scaffold].
|
/// in the body of a [Scaffold].
|
||||||
///
|
///
|
||||||
/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.1.dart **
|
/// ** See code in examples/api/lib/material/navigation_bar/navigation_bar.2.dart **
|
||||||
/// {@end-tool}
|
/// {@end-tool}
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
@ -266,6 +274,8 @@ class NavigationDestination extends StatelessWidget {
|
|||||||
super.key,
|
super.key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
this.selectedIcon,
|
this.selectedIcon,
|
||||||
|
this.indicatorColor,
|
||||||
|
this.indicatorShape,
|
||||||
required this.label,
|
required this.label,
|
||||||
this.tooltip,
|
this.tooltip,
|
||||||
});
|
});
|
||||||
@ -290,6 +300,12 @@ class NavigationDestination extends StatelessWidget {
|
|||||||
/// would use a size of 24.0 and [ColorScheme.onSurface].
|
/// would use a size of 24.0 and [ColorScheme.onSurface].
|
||||||
final Widget? selectedIcon;
|
final Widget? selectedIcon;
|
||||||
|
|
||||||
|
/// The color of the [indicatorShape] when this destination is selected.
|
||||||
|
final Color? indicatorColor;
|
||||||
|
|
||||||
|
/// The shape of the selected inidicator.
|
||||||
|
final ShapeBorder? indicatorShape;
|
||||||
|
|
||||||
/// The text label that appears below the icon of this
|
/// The text label that appears below the icon of this
|
||||||
/// [NavigationDestination].
|
/// [NavigationDestination].
|
||||||
///
|
///
|
||||||
@ -335,8 +351,8 @@ class NavigationDestination extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
NavigationIndicator(
|
NavigationIndicator(
|
||||||
animation: animation,
|
animation: animation,
|
||||||
color: navigationBarTheme.indicatorColor ?? defaults.indicatorColor!,
|
color: indicatorColor ?? navigationBarTheme.indicatorColor ?? defaults.indicatorColor!,
|
||||||
shape: navigationBarTheme.indicatorShape ?? defaults.indicatorShape!
|
shape: indicatorShape ?? navigationBarTheme.indicatorShape ?? defaults.indicatorShape!
|
||||||
),
|
),
|
||||||
_StatusTransitionWidgetBuilder(
|
_StatusTransitionWidgetBuilder(
|
||||||
animation: animation,
|
animation: animation,
|
||||||
@ -440,10 +456,10 @@ class _NavigationDestinationBuilder extends StatelessWidget {
|
|||||||
final double labelPadding;
|
final double labelPadding;
|
||||||
switch (info.labelBehavior) {
|
switch (info.labelBehavior) {
|
||||||
case NavigationDestinationLabelBehavior.alwaysShow:
|
case NavigationDestinationLabelBehavior.alwaysShow:
|
||||||
labelPadding = 10;
|
labelPadding = 8;
|
||||||
break;
|
break;
|
||||||
case NavigationDestinationLabelBehavior.onlyShowSelected:
|
case NavigationDestinationLabelBehavior.onlyShowSelected:
|
||||||
labelPadding = selected ? 10 : 0;
|
labelPadding = selected ? 8 : 0;
|
||||||
break;
|
break;
|
||||||
case NavigationDestinationLabelBehavior.alwaysHide:
|
case NavigationDestinationLabelBehavior.alwaysHide:
|
||||||
labelPadding = 0;
|
labelPadding = 0;
|
||||||
|
@ -589,7 +589,7 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
||||||
Offset indicatorCenter = const Offset(600, 30);
|
Offset indicatorCenter = const Offset(600, 32);
|
||||||
const Size includedIndicatorSize = Size(64, 32);
|
const Size includedIndicatorSize = Size(64, 32);
|
||||||
const Size excludedIndicatorSize = Size(74, 40);
|
const Size excludedIndicatorSize = Size(74, 40);
|
||||||
|
|
||||||
@ -715,7 +715,7 @@ void main() {
|
|||||||
selectedIndex = 1;
|
selectedIndex = 1;
|
||||||
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
await tester.pumpWidget(buildWidget(labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
indicatorCenter = const Offset(600, 30);
|
indicatorCenter = const Offset(600, 32);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
inkFeatures,
|
inkFeatures,
|
||||||
@ -803,6 +803,96 @@ void main() {
|
|||||||
transform = tester.widget<Transform>(transformFinder).transform;
|
transform = tester.widget<Transform>(transformFinder).transform;
|
||||||
expect(transform.getColumn(0)[0], 1.0);
|
expect(transform.getColumn(0)[0], 1.0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('Navigation destination updates indicator color and shape', (WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: true);
|
||||||
|
const Color color = Color(0xff0000ff);
|
||||||
|
const ShapeBorder shape = CircleBorder();
|
||||||
|
|
||||||
|
Widget buildNaviagationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Scaffold(
|
||||||
|
bottomNavigationBar: NavigationBar(
|
||||||
|
destinations: <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(Icons.ac_unit),
|
||||||
|
label: 'AC',
|
||||||
|
indicatorColor: indicatorColor,
|
||||||
|
indicatorShape: indicatorShape,
|
||||||
|
),
|
||||||
|
const NavigationDestination(
|
||||||
|
icon: Icon(Icons.access_alarm),
|
||||||
|
label: 'Alarm',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (int i) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildNaviagationBar());
|
||||||
|
|
||||||
|
// Test default indicator color and shape.
|
||||||
|
expect(_indicator(tester)?.color, theme.colorScheme.secondaryContainer);
|
||||||
|
expect(_indicator(tester)?.shape, const StadiumBorder());
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildNaviagationBar(indicatorColor: color, indicatorShape: shape));
|
||||||
|
|
||||||
|
// Test custom indicator color and shape.
|
||||||
|
expect(_indicator(tester)?.color, color);
|
||||||
|
expect(_indicator(tester)?.shape, shape);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('Material 2', () {
|
||||||
|
// Tests that are only relevant for Material 2. Once ThemeData.useMaterial3
|
||||||
|
// is turned on by default, these tests can be removed.
|
||||||
|
|
||||||
|
testWidgets('Navigation destination updates indicator color and shape', (WidgetTester tester) async {
|
||||||
|
final ThemeData theme = ThemeData(useMaterial3: false);
|
||||||
|
const Color color = Color(0xff0000ff);
|
||||||
|
const ShapeBorder shape = CircleBorder();
|
||||||
|
|
||||||
|
Widget buildNaviagationBar({Color? indicatorColor, ShapeBorder? indicatorShape}) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: theme,
|
||||||
|
home: Scaffold(
|
||||||
|
bottomNavigationBar: NavigationBar(
|
||||||
|
destinations: <Widget>[
|
||||||
|
NavigationDestination(
|
||||||
|
icon: const Icon(Icons.ac_unit),
|
||||||
|
label: 'AC',
|
||||||
|
indicatorColor: indicatorColor,
|
||||||
|
indicatorShape: indicatorShape,
|
||||||
|
),
|
||||||
|
const NavigationDestination(
|
||||||
|
icon: Icon(Icons.access_alarm),
|
||||||
|
label: 'Alarm',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onDestinationSelected: (int i) { },
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildNaviagationBar());
|
||||||
|
|
||||||
|
// Test default indicator color and shape.
|
||||||
|
expect(_indicator(tester)?.color, theme.colorScheme.secondary.withOpacity(0.24));
|
||||||
|
expect(
|
||||||
|
_indicator(tester)?.shape,
|
||||||
|
const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pumpWidget(buildNaviagationBar(indicatorColor: color, indicatorShape: shape));
|
||||||
|
|
||||||
|
// Test custom indicator color and shape.
|
||||||
|
expect(_indicator(tester)?.color, color);
|
||||||
|
expect(_indicator(tester)?.shape, shape);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildWidget(Widget child) {
|
Widget _buildWidget(Widget child) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user