[Material] Expand BottomNavigationBar API (reprise) (#28159)
This commit is contained in:
@@ -189,6 +189,7 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
|
||||
.toList(),
|
||||
currentIndex: _currentIndex,
|
||||
type: _type,
|
||||
//iconSize: 4.0,
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
_navigationViews[_currentIndex].controller.reverse();
|
||||
|
||||
@@ -17,11 +17,6 @@ import 'material_localizations.dart';
|
||||
import 'text_theme.dart';
|
||||
import 'theme.dart';
|
||||
|
||||
const double _kActiveFontSize = 14.0;
|
||||
const double _kInactiveFontSize = 12.0;
|
||||
const double _kTopMargin = 6.0;
|
||||
const double _kBottomMargin = 8.0;
|
||||
|
||||
/// Defines the layout and behavior of a [BottomNavigationBar].
|
||||
///
|
||||
/// See also:
|
||||
@@ -30,18 +25,16 @@ const double _kBottomMargin = 8.0;
|
||||
/// * [BottomNavigationBarItem]
|
||||
/// * <https://material.io/design/components/bottom-navigation.html#specs>
|
||||
enum BottomNavigationBarType {
|
||||
/// The [BottomNavigationBar]'s [BottomNavigationBarItem]s have fixed width, always
|
||||
/// display their text labels, and do not shift when tapped.
|
||||
/// The [BottomNavigationBar]'s [BottomNavigationBarItem]s have fixed width.
|
||||
fixed,
|
||||
|
||||
/// The location and size of the [BottomNavigationBar] [BottomNavigationBarItem]s
|
||||
/// animate and labels fade in when they are tapped. Only the selected item
|
||||
/// displays its text label.
|
||||
/// animate and labels fade in when they are tapped.
|
||||
shifting,
|
||||
}
|
||||
|
||||
/// A material widget displayed at the bottom of an app for selecting among a
|
||||
/// small number of views, typically between three and five.
|
||||
/// A material widget that's displayed at the bottom of an app for selecting
|
||||
/// among a small number of views, typically between three and five.
|
||||
///
|
||||
/// The bottom navigation bar consists of multiple items in the form of
|
||||
/// text labels, icons, or both, laid out on top of a piece of material. It
|
||||
@@ -52,18 +45,19 @@ enum BottomNavigationBarType {
|
||||
/// where it is provided as the [Scaffold.bottomNavigationBar] argument.
|
||||
///
|
||||
/// The bottom navigation bar's [type] changes how its [items] are displayed.
|
||||
/// If not specified it's automatically set to [BottomNavigationBarType.fixed]
|
||||
/// when there are less than four items, [BottomNavigationBarType.shifting]
|
||||
/// otherwise.
|
||||
/// If not specified, then it's automatically set to
|
||||
/// [BottomNavigationBarType.fixed] when there are less than four items, and
|
||||
/// [BottomNavigationBarType.shifting] otherwise.
|
||||
///
|
||||
/// * [BottomNavigationBarType.fixed], the default when there are less than
|
||||
/// four [items]. The selected item is rendered with [fixedColor] if it's
|
||||
/// non-null, otherwise the theme's [ThemeData.primaryColor] is used. The
|
||||
/// navigation bar's background color is the default [Material] background
|
||||
/// four [items]. The selected item is rendered with the
|
||||
/// [selectedItemColor] if it's non-null, otherwise the theme's
|
||||
/// [ThemeData.primaryColor] is used. If [backgroundColor] is null, The
|
||||
/// navigation bar's background color defaults to the [Material] background
|
||||
/// color, [ThemeData.canvasColor] (essentially opaque white).
|
||||
/// * [BottomNavigationBarType.shifting], the default when there are four
|
||||
/// or more [items]. All items are rendered in white and the navigation bar's
|
||||
/// background color is the same as the
|
||||
/// or more [items]. If [selectedItemColor] is null, all items are rendered
|
||||
/// in white. The navigation bar's background color is the same as the
|
||||
/// [BottomNavigationBarItem.backgroundColor] of the selected item. In this
|
||||
/// case it's assumed that each item will have a different background color
|
||||
/// and that background color will contrast well with white.
|
||||
@@ -71,10 +65,9 @@ enum BottomNavigationBarType {
|
||||
/// {@tool snippet --template=stateful_widget_material}
|
||||
/// This example shows a [BottomNavigationBar] as it is used within a [Scaffold]
|
||||
/// widget. The [BottomNavigationBar] has three [BottomNavigationBarItem]
|
||||
/// widgets and the [currentIndex] is set to index 1. The color of the selected
|
||||
/// item is set to a purple color. A function is called whenever any item is
|
||||
/// tapped and the function helps display the appropriate [Text] in the body of
|
||||
/// the [Scaffold].
|
||||
/// widgets and the [currentIndex] is set to index 1. The selected item is
|
||||
/// purple. The `_onItemTapped` function changes the selected item's index
|
||||
/// and displays a corresponding message in the center of the [Scaffold].
|
||||
///
|
||||
/// ```dart
|
||||
/// int _selectedIndex = 1;
|
||||
@@ -106,7 +99,7 @@ enum BottomNavigationBarType {
|
||||
/// BottomNavigationBarItem(icon: Icon(Icons.school), title: Text('School')),
|
||||
/// ],
|
||||
/// currentIndex: _selectedIndex,
|
||||
/// fixedColor: Colors.deepPurple,
|
||||
/// selectedItemColor: Colors.deepPurple,
|
||||
/// onTap: _onItemTapped,
|
||||
/// ),
|
||||
/// );
|
||||
@@ -120,25 +113,43 @@ enum BottomNavigationBarType {
|
||||
/// * [Scaffold]
|
||||
/// * <https://material.io/design/components/bottom-navigation.html>
|
||||
class BottomNavigationBar extends StatefulWidget {
|
||||
/// Creates a bottom navigation bar, typically used in a [Scaffold] where it
|
||||
/// is provided as the [Scaffold.bottomNavigationBar] argument.
|
||||
/// Creates a bottom navigation bar which is typically used as a
|
||||
/// [Scaffold]'s [Scaffold.bottomNavigationBar] argument.
|
||||
///
|
||||
/// The length of [items] must be at least two and each item's icon and title must be not null.
|
||||
/// The length of [items] must be at least two and each item's icon and title
|
||||
/// must not be null.
|
||||
///
|
||||
/// If [type] is null then [BottomNavigationBarType.fixed] is used when there
|
||||
/// are two or three [items], [BottomNavigationBarType.shifting] otherwise.
|
||||
///
|
||||
/// If [fixedColor] is null then the theme's primary color,
|
||||
/// [ThemeData.primaryColor], is used. However if [BottomNavigationBar.type] is
|
||||
/// [BottomNavigationBarType.shifting] then [fixedColor] is ignored.
|
||||
/// The [iconSize], [selectedFontSize], [unselectedFontSize], and [elevation]
|
||||
/// arguments must be non-null and non-negative.
|
||||
///
|
||||
/// Only one of [selectedItemColor] and [fixedColor] can be specified. The
|
||||
/// former is preferred, [fixedColor] only exists for the sake of
|
||||
/// backwards compatibility.
|
||||
///
|
||||
/// The [showSelectedLabels] argument must not be non-null.
|
||||
///
|
||||
/// The [showUnselectedLabels] argument defaults to `true` if [type] is
|
||||
/// [BottomNavigationBarType.fixed] and `false` if [type] is
|
||||
/// [BottomNavigationBarType.shifting].
|
||||
BottomNavigationBar({
|
||||
Key key,
|
||||
@required this.items,
|
||||
this.onTap,
|
||||
this.currentIndex = 0,
|
||||
this.elevation = 8.0,
|
||||
BottomNavigationBarType type,
|
||||
this.fixedColor,
|
||||
Color fixedColor,
|
||||
this.backgroundColor,
|
||||
this.iconSize = 24.0,
|
||||
Color selectedItemColor,
|
||||
this.unselectedItemColor,
|
||||
this.selectedFontSize = 14.0,
|
||||
this.unselectedFontSize = 12.0,
|
||||
this.showSelectedLabels = true,
|
||||
bool showUnselectedLabels,
|
||||
}) : assert(items != null),
|
||||
assert(items.length >= 2),
|
||||
assert(
|
||||
@@ -146,42 +157,125 @@ class BottomNavigationBar extends StatefulWidget {
|
||||
'Every item must have a non-null title',
|
||||
),
|
||||
assert(0 <= currentIndex && currentIndex < items.length),
|
||||
assert(iconSize != null),
|
||||
type = type ?? (items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting),
|
||||
assert(elevation != null && elevation >= 0.0),
|
||||
assert(iconSize != null && iconSize >= 0.0),
|
||||
assert(
|
||||
selectedItemColor != null ? fixedColor == null : true,
|
||||
'Either selectedItemColor or fixedColor can be specified, but not both'
|
||||
),
|
||||
assert(selectedFontSize != null && selectedFontSize >= 0.0),
|
||||
assert(unselectedFontSize != null && unselectedFontSize >= 0.0),
|
||||
assert(showSelectedLabels != null),
|
||||
type = _type(type, items),
|
||||
selectedItemColor = selectedItemColor ?? fixedColor,
|
||||
showUnselectedLabels = showUnselectedLabels ?? _defaultShowUnselected(_type(type, items)),
|
||||
super(key: key);
|
||||
|
||||
/// The interactive items laid out within the bottom navigation bar where each item has an icon and title.
|
||||
/// Defines the appearance of the button items that are arrayed within the
|
||||
/// bottom navigation bar.
|
||||
final List<BottomNavigationBarItem> items;
|
||||
|
||||
/// The callback that is called when a item is tapped.
|
||||
/// Called when one of the [items] is tapped.
|
||||
///
|
||||
/// The widget creating the bottom navigation bar needs to keep track of the
|
||||
/// current index and call `setState` to rebuild it with the newly provided
|
||||
/// index.
|
||||
/// The stateful widget that creates the bottom navigation bar needs to keep
|
||||
/// track of the index of the selected [BottomNavigationBarItem] and call
|
||||
/// `setState` to rebuild the bottom navigation bar with the new [currentIndex].
|
||||
final ValueChanged<int> onTap;
|
||||
|
||||
/// The index into [items] of the current active item.
|
||||
/// The index into [items] for the current active [BottomNavigationBarItem].
|
||||
final int currentIndex;
|
||||
|
||||
/// The z-coordinate of this [BottomNavigationBar].
|
||||
///
|
||||
/// If null, defaults to `8.0`.
|
||||
///
|
||||
/// {@macro flutter.material.material.elevation}
|
||||
final double elevation;
|
||||
|
||||
/// Defines the layout and behavior of a [BottomNavigationBar].
|
||||
///
|
||||
/// See documentation for [BottomNavigationBarType] for information on the meaning
|
||||
/// of different types.
|
||||
/// See documentation for [BottomNavigationBarType] for information on the
|
||||
/// meaning of different types.
|
||||
final BottomNavigationBarType type;
|
||||
|
||||
/// The color of the selected item when bottom navigation bar is
|
||||
/// [BottomNavigationBarType.fixed].
|
||||
/// The value of [selectedItemColor].
|
||||
///
|
||||
/// If [fixedColor] is null then the theme's primary color,
|
||||
/// [ThemeData.primaryColor], is used. However if [BottomNavigationBar.type] is
|
||||
/// [BottomNavigationBarType.shifting] then [fixedColor] is ignored.
|
||||
final Color fixedColor;
|
||||
/// This getter only exists for backwards compatibility, the
|
||||
/// [selectedItemColor] property is preferred.
|
||||
Color get fixedColor => selectedItemColor;
|
||||
|
||||
/// The color of the [BottomNavigationBar] itself.
|
||||
///
|
||||
/// If [type] is [BottomNavigationBarType.shifting] and the
|
||||
/// [items]s, have [BottomNavigationBarItem.backgroundColor] set, the [item]'s
|
||||
/// backgroundColor will splash and overwrite this color.
|
||||
final Color backgroundColor;
|
||||
|
||||
/// The size of all of the [BottomNavigationBarItem] icons.
|
||||
///
|
||||
/// See [BottomNavigationBarItem.icon] for more information.
|
||||
final double iconSize;
|
||||
|
||||
/// The color of the selected [BottomNavigationBarItem.icon] and
|
||||
/// [BottomNavigationBarItem.label].
|
||||
///
|
||||
/// If null then the [ThemeData.primaryColor] is used.
|
||||
final Color selectedItemColor;
|
||||
|
||||
/// The color of the unselected [BottomNavigationBarItem.icon] and
|
||||
/// [BottomNavigationBarItem.label]s.
|
||||
///
|
||||
/// If null then the [TextTheme.caption]'s color is used.
|
||||
final Color unselectedItemColor;
|
||||
|
||||
/// The font size of the [BottomNavigationBarItem] labels when they are selected.
|
||||
///
|
||||
/// Defaults to `14.0`.
|
||||
final double selectedFontSize;
|
||||
|
||||
/// The font size of the [BottomNavigationBarItem] labels when they are not
|
||||
/// selected.
|
||||
///
|
||||
/// Defaults to `12.0`.
|
||||
final double unselectedFontSize;
|
||||
|
||||
/// Whether the labels are shown for the selected [BottomNavigationBarItem].
|
||||
final bool showUnselectedLabels;
|
||||
|
||||
/// Whether the labels are shown for the unselected [BottomNavigationBarItem]s.
|
||||
final bool showSelectedLabels;
|
||||
|
||||
// Used by the [BottomNavigationBar] constructor to set the [type] parameter.
|
||||
//
|
||||
// If type is provided, it is returned. Otherwise,
|
||||
// [BottomNavigationBarType.fixed] is used for 3 or fewer items, and
|
||||
// [BottomNavigationBarType.shifting] is used for 4+ items.
|
||||
static BottomNavigationBarType _type(
|
||||
BottomNavigationBarType type,
|
||||
List<BottomNavigationBarItem> items,
|
||||
) {
|
||||
if (type != null) {
|
||||
return type;
|
||||
}
|
||||
return items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting;
|
||||
}
|
||||
|
||||
// Used by the [BottomNavigationBar] constructor to set the [showUnselected]
|
||||
// parameter.
|
||||
//
|
||||
// Unselected labels are shown by default for [BottomNavigationBarType.fixed],
|
||||
// and hidden by default for [BottomNavigationBarType.shifting].
|
||||
static bool _defaultShowUnselected(BottomNavigationBarType type) {
|
||||
switch (type) {
|
||||
case BottomNavigationBarType.shifting:
|
||||
return false;
|
||||
case BottomNavigationBarType.fixed:
|
||||
return true;
|
||||
}
|
||||
assert(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
_BottomNavigationBarState createState() => _BottomNavigationBarState();
|
||||
}
|
||||
@@ -198,8 +292,17 @@ class _BottomNavigationTile extends StatelessWidget {
|
||||
this.colorTween,
|
||||
this.flex,
|
||||
this.selected = false,
|
||||
@required this.selectedFontSize,
|
||||
@required this.unselectedFontSize,
|
||||
this.showSelectedLabels,
|
||||
this.showUnselectedLabels,
|
||||
this.indexLabel,
|
||||
}) : assert(selected != null);
|
||||
}) : assert(type != null),
|
||||
assert(item != null),
|
||||
assert(animation != null),
|
||||
assert(selected != null),
|
||||
assert(selectedFontSize != null && selectedFontSize >= 0),
|
||||
assert(unselectedFontSize != null && unselectedFontSize >= 0);
|
||||
|
||||
final BottomNavigationBarType type;
|
||||
final BottomNavigationBarItem item;
|
||||
@@ -209,7 +312,11 @@ class _BottomNavigationTile extends StatelessWidget {
|
||||
final ColorTween colorTween;
|
||||
final double flex;
|
||||
final bool selected;
|
||||
final double selectedFontSize;
|
||||
final double unselectedFontSize;
|
||||
final String indexLabel;
|
||||
final bool showSelectedLabels;
|
||||
final bool showUnselectedLabels;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -218,16 +325,50 @@ class _BottomNavigationTile extends StatelessWidget {
|
||||
// produce smooth animation. We do this by multiplying the flex value
|
||||
// (which is an integer) by a large number.
|
||||
int size;
|
||||
Widget label;
|
||||
|
||||
double bottomPadding = selectedFontSize / 2.0;
|
||||
double topPadding = selectedFontSize / 2.0;
|
||||
|
||||
// Defines the padding for the animating icons + labels.
|
||||
//
|
||||
// The animations go from "Unselected":
|
||||
// =======
|
||||
// | <-- Padding equal to the text height.
|
||||
// | ☆
|
||||
// | text <-- Invisible text.
|
||||
// =======
|
||||
//
|
||||
// To "Selected":
|
||||
//
|
||||
// =======
|
||||
// | <-- Padding equal to 1/2 text height.
|
||||
// | ☆
|
||||
// | text
|
||||
// | <-- Padding equal to 1/2 text height.
|
||||
// =======
|
||||
if (showSelectedLabels && !showUnselectedLabels) {
|
||||
bottomPadding = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: selectedFontSize / 2.0,
|
||||
).evaluate(animation);
|
||||
topPadding = Tween<double>(
|
||||
begin: selectedFontSize,
|
||||
end: selectedFontSize / 2.0,
|
||||
).evaluate(animation);
|
||||
}
|
||||
|
||||
// Center all icons if no labels are shown.
|
||||
if (!showSelectedLabels && !showUnselectedLabels) {
|
||||
bottomPadding = 0.0;
|
||||
topPadding = selectedFontSize;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case BottomNavigationBarType.fixed:
|
||||
size = 1;
|
||||
label = _FixedLabel(colorTween: colorTween, animation: animation, item: item);
|
||||
break;
|
||||
case BottomNavigationBarType.shifting:
|
||||
size = (flex * 1000.0).round();
|
||||
label = _ShiftingLabel(animation: animation, item: item);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -241,21 +382,31 @@ class _BottomNavigationTile extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
InkResponse(
|
||||
onTap: onTap,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
_TileIcon(
|
||||
type: type,
|
||||
colorTween: colorTween,
|
||||
animation: animation,
|
||||
iconSize: iconSize,
|
||||
selected: selected,
|
||||
item: item,
|
||||
),
|
||||
label,
|
||||
],
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(top: topPadding, bottom: bottomPadding),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
_TileIcon(
|
||||
colorTween: colorTween,
|
||||
animation: animation,
|
||||
iconSize: iconSize,
|
||||
selected: selected,
|
||||
item: item,
|
||||
),
|
||||
_Label(
|
||||
colorTween: colorTween,
|
||||
animation: animation,
|
||||
item: item,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
showSelectedLabels: showSelectedLabels,
|
||||
showUnselectedLabels: showUnselectedLabels,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Semantics(
|
||||
@@ -272,15 +423,15 @@ class _BottomNavigationTile extends StatelessWidget {
|
||||
class _TileIcon extends StatelessWidget {
|
||||
const _TileIcon({
|
||||
Key key,
|
||||
@required this.type,
|
||||
@required this.colorTween,
|
||||
@required this.animation,
|
||||
@required this.iconSize,
|
||||
@required this.selected,
|
||||
@required this.item,
|
||||
}) : super(key: key);
|
||||
}) : assert(selected != null),
|
||||
assert(item != null),
|
||||
super(key: key);
|
||||
|
||||
final BottomNavigationBarType type;
|
||||
final ColorTween colorTween;
|
||||
final Animation<double> animation;
|
||||
final double iconSize;
|
||||
@@ -289,28 +440,11 @@ class _TileIcon extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double tweenStart;
|
||||
Color iconColor;
|
||||
switch (type) {
|
||||
case BottomNavigationBarType.fixed:
|
||||
tweenStart = 8.0;
|
||||
iconColor = colorTween.evaluate(animation);
|
||||
break;
|
||||
case BottomNavigationBarType.shifting:
|
||||
tweenStart = 16.0;
|
||||
iconColor = Colors.white;
|
||||
break;
|
||||
}
|
||||
final Color iconColor = colorTween.evaluate(animation);
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: 1.0,
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(
|
||||
top: Tween<double>(
|
||||
begin: tweenStart,
|
||||
end: _kTopMargin,
|
||||
).evaluate(animation),
|
||||
),
|
||||
child: IconTheme(
|
||||
data: IconThemeData(
|
||||
color: iconColor,
|
||||
@@ -323,89 +457,84 @@ class _TileIcon extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _FixedLabel extends StatelessWidget {
|
||||
const _FixedLabel({
|
||||
class _Label extends StatelessWidget {
|
||||
const _Label({
|
||||
Key key,
|
||||
@required this.colorTween,
|
||||
@required this.animation,
|
||||
@required this.item,
|
||||
}) : super(key: key);
|
||||
@required this.selectedFontSize,
|
||||
@required this.unselectedFontSize,
|
||||
@required this.showSelectedLabels,
|
||||
@required this.showUnselectedLabels,
|
||||
}) : assert(colorTween != null),
|
||||
assert(animation != null),
|
||||
assert(item != null),
|
||||
assert(selectedFontSize != null),
|
||||
assert(unselectedFontSize != null),
|
||||
assert(showSelectedLabels != null),
|
||||
assert(showUnselectedLabels != null),
|
||||
super(key: key);
|
||||
|
||||
final ColorTween colorTween;
|
||||
final Animation<double> animation;
|
||||
final BottomNavigationBarItem item;
|
||||
final double selectedFontSize;
|
||||
final double unselectedFontSize;
|
||||
final bool showSelectedLabels;
|
||||
final bool showUnselectedLabels;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
heightFactor: 1.0,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(bottom: _kBottomMargin),
|
||||
child: DefaultTextStyle.merge(
|
||||
style: TextStyle(
|
||||
fontSize: _kActiveFontSize,
|
||||
color: colorTween.evaluate(animation),
|
||||
),
|
||||
// The font size should grow here when active, but because of the way
|
||||
// font rendering works, it doesn't grow smoothly if we just animate
|
||||
// the font size, so we use a transform instead.
|
||||
child: Transform(
|
||||
transform: Matrix4.diagonal3(
|
||||
Vector3.all(
|
||||
Tween<double>(
|
||||
begin: _kInactiveFontSize / _kActiveFontSize,
|
||||
end: 1.0,
|
||||
).evaluate(animation),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: item.title,
|
||||
Widget text = DefaultTextStyle.merge(
|
||||
style: TextStyle(
|
||||
fontSize: selectedFontSize,
|
||||
color: colorTween.evaluate(animation),
|
||||
),
|
||||
// The font size should grow here when active, but because of the way
|
||||
// font rendering works, it doesn't grow smoothly if we just animate
|
||||
// the font size, so we use a transform instead.
|
||||
child: Transform(
|
||||
transform: Matrix4.diagonal3(
|
||||
Vector3.all(
|
||||
Tween<double>(
|
||||
begin: unselectedFontSize / selectedFontSize,
|
||||
end: 1.0,
|
||||
).evaluate(animation),
|
||||
),
|
||||
),
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: item.title,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ShiftingLabel extends StatelessWidget {
|
||||
const _ShiftingLabel({
|
||||
Key key,
|
||||
@required this.animation,
|
||||
@required this.item,
|
||||
}) : super(key: key);
|
||||
if (!showUnselectedLabels && !showSelectedLabels) {
|
||||
// Never show any labels.
|
||||
text = Opacity(
|
||||
alwaysIncludeSemantics: true,
|
||||
opacity: 0.0,
|
||||
child: text,
|
||||
);
|
||||
} else if (!showUnselectedLabels) {
|
||||
// Fade selected labels in.
|
||||
text = FadeTransition(
|
||||
alwaysIncludeSemantics: true,
|
||||
opacity: animation,
|
||||
child: text,
|
||||
);
|
||||
} else if (!showSelectedLabels) {
|
||||
// Fade selected labels out.
|
||||
text = FadeTransition(
|
||||
alwaysIncludeSemantics: true,
|
||||
opacity: Tween<double>(begin: 1.0, end: 0.0).animate(animation),
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
final Animation<double> animation;
|
||||
final BottomNavigationBarItem item;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
heightFactor: 1.0,
|
||||
child: Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: Tween<double>(
|
||||
// In the spec, they just remove the label for inactive items and
|
||||
// specify a 16dp bottom margin. We don't want to actually remove
|
||||
// the label because we want to fade it in and out, so this modifies
|
||||
// the bottom margin to take that into account.
|
||||
begin: 2.0,
|
||||
end: _kBottomMargin,
|
||||
).evaluate(animation),
|
||||
),
|
||||
child: FadeTransition(
|
||||
alwaysIncludeSemantics: true,
|
||||
opacity: animation,
|
||||
child: DefaultTextStyle.merge(
|
||||
style: const TextStyle(
|
||||
fontSize: _kActiveFontSize,
|
||||
color: Colors.white,
|
||||
),
|
||||
child: item.title,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Container(child: text),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -529,63 +658,57 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
|
||||
List<Widget> _createTiles() {
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
assert(localizations != null);
|
||||
final List<Widget> children = <Widget>[];
|
||||
switch (widget.type) {
|
||||
case BottomNavigationBarType.fixed:
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
final TextTheme textTheme = themeData.textTheme;
|
||||
Color themeColor;
|
||||
switch (themeData.brightness) {
|
||||
case Brightness.light:
|
||||
themeColor = themeData.primaryColor;
|
||||
break;
|
||||
case Brightness.dark:
|
||||
themeColor = themeData.accentColor;
|
||||
break;
|
||||
}
|
||||
final ColorTween colorTween = ColorTween(
|
||||
begin: textTheme.caption.color,
|
||||
end: widget.fixedColor ?? themeColor,
|
||||
);
|
||||
for (int i = 0; i < widget.items.length; i += 1) {
|
||||
children.add(
|
||||
_BottomNavigationTile(
|
||||
widget.type,
|
||||
widget.items[i],
|
||||
_animations[i],
|
||||
widget.iconSize,
|
||||
onTap: () {
|
||||
if (widget.onTap != null)
|
||||
widget.onTap(i);
|
||||
},
|
||||
colorTween: colorTween,
|
||||
selected: i == widget.currentIndex,
|
||||
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: widget.items.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
|
||||
Color themeColor;
|
||||
switch (themeData.brightness) {
|
||||
case Brightness.light:
|
||||
themeColor = themeData.primaryColor;
|
||||
break;
|
||||
case BottomNavigationBarType.shifting:
|
||||
for (int i = 0; i < widget.items.length; i += 1) {
|
||||
children.add(
|
||||
_BottomNavigationTile(
|
||||
widget.type,
|
||||
widget.items[i],
|
||||
_animations[i],
|
||||
widget.iconSize,
|
||||
onTap: () {
|
||||
if (widget.onTap != null)
|
||||
widget.onTap(i);
|
||||
},
|
||||
flex: _evaluateFlex(_animations[i]),
|
||||
selected: i == widget.currentIndex,
|
||||
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: widget.items.length),
|
||||
),
|
||||
);
|
||||
}
|
||||
case Brightness.dark:
|
||||
themeColor = themeData.accentColor;
|
||||
break;
|
||||
}
|
||||
return children;
|
||||
|
||||
ColorTween colorTween;
|
||||
switch (widget.type) {
|
||||
case BottomNavigationBarType.fixed:
|
||||
colorTween = ColorTween(
|
||||
begin: widget.unselectedItemColor ?? themeData.textTheme.caption.color,
|
||||
end: widget.selectedItemColor ?? widget.fixedColor ?? themeColor,
|
||||
);
|
||||
break;
|
||||
case BottomNavigationBarType.shifting:
|
||||
colorTween = ColorTween(
|
||||
begin: widget.unselectedItemColor ?? Colors.white,
|
||||
end: widget.selectedItemColor ?? Colors.white,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
final List<Widget> tiles = <Widget>[];
|
||||
for (int i = 0; i < widget.items.length; i++) {
|
||||
tiles.add(_BottomNavigationTile(
|
||||
widget.type,
|
||||
widget.items[i],
|
||||
_animations[i],
|
||||
widget.iconSize,
|
||||
selectedFontSize: widget.selectedFontSize,
|
||||
unselectedFontSize: widget.unselectedFontSize,
|
||||
onTap: () {
|
||||
if (widget.onTap != null)
|
||||
widget.onTap(i);
|
||||
},
|
||||
colorTween: colorTween,
|
||||
flex: _evaluateFlex(_animations[i]),
|
||||
selected: i == widget.currentIndex,
|
||||
showSelectedLabels: widget.showSelectedLabels,
|
||||
showUnselectedLabels: widget.showUnselectedLabels,
|
||||
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: widget.items.length),
|
||||
));
|
||||
}
|
||||
return tiles;
|
||||
}
|
||||
|
||||
Widget _createContainer(List<Widget> tiles) {
|
||||
@@ -602,12 +725,14 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
|
||||
Widget build(BuildContext context) {
|
||||
assert(debugCheckHasDirectionality(context));
|
||||
assert(debugCheckHasMaterialLocalizations(context));
|
||||
assert(debugCheckHasMediaQuery(context));
|
||||
|
||||
// Labels apply up to _bottomMargin padding. Remainder is media padding.
|
||||
final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - _kBottomMargin, 0.0);
|
||||
final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - widget.selectedFontSize / 2.0, 0.0);
|
||||
Color backgroundColor;
|
||||
switch (widget.type) {
|
||||
case BottomNavigationBarType.fixed:
|
||||
backgroundColor = widget.backgroundColor;
|
||||
break;
|
||||
case BottomNavigationBarType.shifting:
|
||||
backgroundColor = _backgroundColor;
|
||||
@@ -616,7 +741,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
|
||||
return Semantics(
|
||||
explicitChildNodes: true,
|
||||
child: Material(
|
||||
elevation: 8.0,
|
||||
elevation: widget.elevation,
|
||||
color: backgroundColor,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
|
||||
|
||||
@@ -9,8 +9,8 @@ import 'framework.dart';
|
||||
/// An interactive button within either material's [BottomNavigationBar]
|
||||
/// or the iOS themed [CupertinoTabBar] with an icon and title.
|
||||
///
|
||||
/// This class is rarely used in isolation. Commonly embedded in one of the
|
||||
/// bottom navigation widgets above.
|
||||
/// This class is rarely used in isolation. It is typically embedded in one of
|
||||
/// the bottom navigation widgets above.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@@ -67,7 +67,7 @@ class BottomNavigationBarItem {
|
||||
///
|
||||
/// If the navigation bar's type is [BottomNavigationBarType.shifting], then
|
||||
/// the entire bar is flooded with the [backgroundColor] when this item is
|
||||
/// tapped.
|
||||
/// tapped. This will override [BottomNavigationBar.backgroundColor].
|
||||
///
|
||||
/// Not used for [CupertinoTabBar]. Control the invariant bar color directly
|
||||
/// via [CupertinoTabBar.backgroundColor].
|
||||
|
||||
@@ -6,7 +6,9 @@ import 'dart:io';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' show Vector3;
|
||||
|
||||
import '../rendering/mock_canvas.dart';
|
||||
import '../widgets/semantics_tester.dart';
|
||||
@@ -68,6 +70,325 @@ void main() {
|
||||
expect(find.text('Alarm'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Fixed BottomNavigationBar defaults', (WidgetTester tester) async {
|
||||
const Color primaryColor = Colors.black;
|
||||
const Color captionColor = Colors.purple;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
primaryColor: primaryColor,
|
||||
textTheme: const TextTheme(caption: TextStyle(color: captionColor)),
|
||||
),
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const double selectedFontSize = 14.0;
|
||||
const double unselectedFontSize = 12.0;
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.fontSize, selectedFontSize);
|
||||
// Unselected label has a font size of 14 but is scaled down to be font size 12.
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style.fontSize, selectedFontSize);
|
||||
expect(
|
||||
tester.firstWidget<Transform>(find.ancestor(of: find.text('Alarm'), matching: find.byType(Transform))).transform,
|
||||
equals(Matrix4.diagonal3(Vector3.all(unselectedFontSize / selectedFontSize))),
|
||||
);
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.color, equals(primaryColor));
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style.color, equals(captionColor));
|
||||
expect(_getOpacity(tester, 'Alarm'), equals(1.0));
|
||||
expect(_getMaterial(tester).elevation, equals(8.0));
|
||||
});
|
||||
|
||||
testWidgets('Shifting BottomNavigationBar defaults', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.shifting,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const double selectedFontSize = 14.0;
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.fontSize, selectedFontSize);
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.color, equals(Colors.white));
|
||||
expect(_getOpacity(tester, 'Alarm'), equals(0.0));
|
||||
expect(_getMaterial(tester).elevation, equals(8.0));
|
||||
});
|
||||
|
||||
testWidgets('Fixed BottomNavigationBar custom font size, color', (WidgetTester tester) async {
|
||||
const Color primaryColor = Colors.black;
|
||||
const Color captionColor = Colors.purple;
|
||||
const Color selectedColor = Colors.blue;
|
||||
const Color unselectedColor = Colors.yellow;
|
||||
const double selectedFontSize = 18.0;
|
||||
const double unselectedFontSize = 14.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
primaryColor: primaryColor,
|
||||
textTheme: const TextTheme(caption: TextStyle(color: captionColor)),
|
||||
),
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
selectedItemColor: selectedColor,
|
||||
unselectedItemColor: unselectedColor,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.fontSize, selectedFontSize);
|
||||
// Unselected label has a font size of 18 but is scaled down to be font size 14.
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style.fontSize, selectedFontSize);
|
||||
expect(
|
||||
tester.firstWidget<Transform>(find.ancestor(of: find.text('Alarm'), matching: find.byType(Transform))).transform,
|
||||
equals(Matrix4.diagonal3(Vector3.all(unselectedFontSize / selectedFontSize))),
|
||||
);
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.color, equals(selectedColor));
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style.color, equals(unselectedColor));
|
||||
expect(_getOpacity(tester, 'Alarm'), equals(1.0));
|
||||
});
|
||||
|
||||
|
||||
testWidgets('Shifting BottomNavigationBar custom font size, color', (WidgetTester tester) async {
|
||||
const Color primaryColor = Colors.black;
|
||||
const Color captionColor = Colors.purple;
|
||||
const Color selectedColor = Colors.blue;
|
||||
const Color unselectedColor = Colors.yellow;
|
||||
const double selectedFontSize = 18.0;
|
||||
const double unselectedFontSize = 14.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: ThemeData(
|
||||
primaryColor: primaryColor,
|
||||
textTheme: const TextTheme(caption: TextStyle(color: captionColor)),
|
||||
),
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.shifting,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
selectedItemColor: selectedColor,
|
||||
unselectedItemColor: unselectedColor,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.fontSize, selectedFontSize);
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.color, equals(selectedColor));
|
||||
expect(_getOpacity(tester, 'Alarm'), equals(0.0));
|
||||
});
|
||||
|
||||
testWidgets('Fixed BottomNavigationBar can hide unselected labels', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
showUnselectedLabels: false,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(_getOpacity(tester, 'AC'), equals(1.0));
|
||||
expect(_getOpacity(tester, 'Alarm'), equals(0.0));
|
||||
});
|
||||
|
||||
testWidgets('Fixed BottomNavigationBar can update background color', (WidgetTester tester) async {
|
||||
const Color color = Colors.yellow;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
backgroundColor: color,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(_getMaterial(tester).color, equals(color));
|
||||
});
|
||||
|
||||
testWidgets('Shifting BottomNavigationBar background color is overriden by item color', (WidgetTester tester) async {
|
||||
const Color itemColor = Colors.yellow;
|
||||
const Color backgroundColor = Colors.blue;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.shifting,
|
||||
backgroundColor: backgroundColor,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
backgroundColor: itemColor,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(_getMaterial(tester).color, equals(itemColor));
|
||||
});
|
||||
|
||||
testWidgets('Specifying both selectedItemColor and fixedColor asserts', (WidgetTester tester) async {
|
||||
expect(
|
||||
() {
|
||||
return BottomNavigationBar(
|
||||
selectedItemColor: Colors.black,
|
||||
fixedColor: Colors.black,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
throwsAssertionError,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Fixed BottomNavigationBar uses fixedColor when selectedItemColor not provided', (WidgetTester tester) async {
|
||||
const Color fixedColor = Colors.black;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
fixedColor: fixedColor,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.color, equals(fixedColor));
|
||||
});
|
||||
|
||||
testWidgets('setting selectedFontSize to zero hides all labels', (WidgetTester tester) async {
|
||||
const double customElevation = 3.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
elevation: customElevation,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
expect(_getMaterial(tester).elevation, equals(customElevation));
|
||||
});
|
||||
|
||||
testWidgets('BottomNavigationBar adds bottom padding to height', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
@@ -91,7 +412,7 @@ void main() {
|
||||
)
|
||||
);
|
||||
|
||||
const double labelBottomMargin = 8.0; // _kBottomMargin in implementation.
|
||||
const double labelBottomMargin = 7.0; // 7 == defaulted selectedFontSize / 2.0.
|
||||
const double additionalPadding = 40.0 - labelBottomMargin;
|
||||
const double expectedHeight = kBottomNavigationBarHeight + additionalPadding;
|
||||
expect(tester.getSize(find.byType(BottomNavigationBar)).height, expectedHeight);
|
||||
@@ -393,7 +714,7 @@ void main() {
|
||||
);
|
||||
|
||||
final RenderBox box = tester.renderObject(find.byType(BottomNavigationBar));
|
||||
expect(box.size.height, equals(68.0));
|
||||
expect(box.size.height, equals(66.0));
|
||||
});
|
||||
|
||||
testWidgets('BottomNavigationBar limits width of tiles with long titles', (WidgetTester tester) async {
|
||||
@@ -827,7 +1148,7 @@ void main() {
|
||||
await tester.pump(const Duration(milliseconds: 30));
|
||||
await expectLater(
|
||||
find.byType(BottomNavigationBar),
|
||||
matchesGoldenFile('bottom_navigation_bar.shifting_transition.$pump.png'),
|
||||
matchesGoldenFile('bottom_navigation_bar.shifting_transition.2.$pump.png'),
|
||||
skip: !Platform.isLinux,
|
||||
);
|
||||
}
|
||||
@@ -851,6 +1172,191 @@ void main() {
|
||||
])));
|
||||
}, throwsA(isInstanceOf<AssertionError>()));
|
||||
});
|
||||
|
||||
testWidgets('BottomNavigationBar [showSelectedLabels]=false and [showUnselectedLabels]=false '
|
||||
'for shifting navbar, expect that there is no rendered text', (WidgetTester tester) async {
|
||||
final Widget widget = MaterialApp(
|
||||
home: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
type: BottomNavigationBarType.shifting,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Red'),
|
||||
backgroundColor: Colors.red,
|
||||
icon: Icon(Icons.dashboard),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Green'),
|
||||
backgroundColor: Colors.green,
|
||||
icon: Icon(Icons.menu),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
await tester.pumpWidget(widget);
|
||||
expect(find.text('Red'), findsOneWidget);
|
||||
expect(find.text('Green'), findsOneWidget);
|
||||
expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.0);
|
||||
expect(tester.widget<Opacity>(find.byType(Opacity).last).opacity, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('BottomNavigationBar [showSelectedLabels]=false and [showUnselectedLabels]=false '
|
||||
'for fixed navbar, expect that there is no rendered text', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
type: BottomNavigationBarType.fixed,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Red'),
|
||||
backgroundColor: Colors.red,
|
||||
icon: Icon(Icons.dashboard),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
title: Text('Green'),
|
||||
backgroundColor: Colors.green,
|
||||
icon: Icon(Icons.menu),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(find.text('Red'), findsOneWidget);
|
||||
expect(find.text('Green'), findsOneWidget);
|
||||
expect(tester.widget<Opacity>(find.byType(Opacity).first).opacity, 0.0);
|
||||
expect(tester.widget<Opacity>(find.byType(Opacity).last).opacity, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('BottomNavigationBar.fixed [showSelectedLabels]=false and [showUnselectedLabels]=false semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
textDirection: TextDirection.ltr,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('Red'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Green'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expected = TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isSelected,
|
||||
SemanticsFlag.isHeader,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: 'Red\nTab 1 of 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHeader,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: 'Green\nTab 2 of 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
expect(semantics, hasSemantics(expected, ignoreId: true, ignoreTransform: true, ignoreRect: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('BottomNavigationBar.shifting [showSelectedLabels]=false and [showUnselectedLabels]=false semantics', (WidgetTester tester) async {
|
||||
final SemanticsTester semantics = SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
boilerplate(
|
||||
textDirection: TextDirection.ltr,
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
type: BottomNavigationBarType.shifting,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('Red'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Green'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TestSemantics expected = TestSemantics.root(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
children: <TestSemantics>[
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isSelected,
|
||||
SemanticsFlag.isHeader,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: 'Red\nTab 1 of 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
TestSemantics(
|
||||
flags: <SemanticsFlag>[
|
||||
SemanticsFlag.isHeader,
|
||||
],
|
||||
actions: <SemanticsAction>[SemanticsAction.tap],
|
||||
label: 'Green\nTab 2 of 2',
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
expect(semantics, hasSemantics(expected, ignoreId: true, ignoreTransform: true, ignoreRect: true));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Widget boilerplate({ Widget bottomNavigationBar, @required TextDirection textDirection }) {
|
||||
@@ -874,3 +1380,19 @@ Widget boilerplate({ Widget bottomNavigationBar, @required TextDirection textDir
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double _getOpacity(WidgetTester tester, String textValue) {
|
||||
final FadeTransition opacityWidget = tester.widget<FadeTransition>(
|
||||
find.ancestor(
|
||||
of: find.text(textValue),
|
||||
matching: find.byType(FadeTransition),
|
||||
).first
|
||||
);
|
||||
return opacityWidget.opacity.value;
|
||||
}
|
||||
|
||||
Material _getMaterial(WidgetTester tester) {
|
||||
return tester.firstWidget<Material>(
|
||||
find.descendant(of: find.byType(BottomNavigationBar), matching: find.byType(Material)),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user