mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
[Material] selected/unselected label styles + icon themes on BottomNavigationBar (#31018)
* add text style params * add icon theme params * Added tests
This commit is contained in:
parent
1d91bd2583
commit
a40e5c90f0
|
@ -189,7 +189,6 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
|
|||
.toList(),
|
||||
currentIndex: _currentIndex,
|
||||
type: _type,
|
||||
//iconSize: 4.0,
|
||||
onTap: (int index) {
|
||||
setState(() {
|
||||
_navigationViews[_currentIndex].controller.reverse();
|
||||
|
|
|
@ -147,6 +147,17 @@ class BottomNavigationBar extends StatefulWidget {
|
|||
/// The [iconSize], [selectedFontSize], [unselectedFontSize], and [elevation]
|
||||
/// arguments must be non-null and non-negative.
|
||||
///
|
||||
/// If [selectedLabelStyle.color] and [unselectedLabelStyle.color] values
|
||||
/// are non-null, they will be used instead of [selectedItemColor] and
|
||||
/// [unselectedItemColor].
|
||||
///
|
||||
/// If custom [IconThemData]s are used, you must provide both
|
||||
/// [selectedIconTheme] and [unselectedIconTheme], and both
|
||||
/// [IconThemeData.color] and [IconThemeData.size] must be set.
|
||||
///
|
||||
/// If both [selectedLabelStyle.fontSize] and [selectedFontSize] are set,
|
||||
/// [selectedLabelStyle.fontSize] will be used.
|
||||
///
|
||||
/// Only one of [selectedItemColor] and [fixedColor] can be specified. The
|
||||
/// former is preferred, [fixedColor] only exists for the sake of
|
||||
/// backwards compatibility.
|
||||
|
@ -168,8 +179,12 @@ class BottomNavigationBar extends StatefulWidget {
|
|||
this.iconSize = 24.0,
|
||||
Color selectedItemColor,
|
||||
this.unselectedItemColor,
|
||||
this.selectedIconTheme = const IconThemeData(),
|
||||
this.unselectedIconTheme = const IconThemeData(),
|
||||
this.selectedFontSize = 14.0,
|
||||
this.unselectedFontSize = 12.0,
|
||||
this.selectedLabelStyle,
|
||||
this.unselectedLabelStyle,
|
||||
this.showSelectedLabels = true,
|
||||
bool showUnselectedLabels,
|
||||
}) : assert(items != null),
|
||||
|
@ -250,14 +265,48 @@ class BottomNavigationBar extends StatefulWidget {
|
|||
/// If null then the [TextTheme.caption]'s color is used.
|
||||
final Color unselectedItemColor;
|
||||
|
||||
/// The size, opacity, and color of the icon in the currently selected
|
||||
/// [BottomNavigationBarItem.icon].
|
||||
///
|
||||
/// If this is not provided, the size will default to [iconSize], the color
|
||||
/// will default to [selectedItemColor].
|
||||
///
|
||||
/// It this field is provided, it must contain non-null [IconThemeData.size]
|
||||
/// and [IconThemeData.color] properties. Also, if this field is supplied,
|
||||
/// [unselectedIconTheme] must be provided.
|
||||
final IconThemeData selectedIconTheme;
|
||||
|
||||
/// The size, opacity, and color of the icon in the currently unselected
|
||||
/// [BottomNavigationBarItem.icon]s
|
||||
///
|
||||
/// If this is not provided, the size will default to [iconSize], the color
|
||||
/// will default to [unselectedItemColor].
|
||||
///
|
||||
/// It this field is provided, it must contain non-null [IconThemeData.size]
|
||||
/// and [IconThemeData.color] properties. Also, if this field is supplied,
|
||||
/// [unselectedIconTheme] must be provided.
|
||||
final IconThemeData unselectedIconTheme;
|
||||
|
||||
/// The [TextStyle] of the [BottomNavigationBarItem] labels when they are
|
||||
/// selected.
|
||||
final TextStyle selectedLabelStyle;
|
||||
|
||||
/// The [TextStyle] of the [BottomNavigationBarItem] labels when they are not
|
||||
/// selected.
|
||||
final TextStyle unselectedLabelStyle;
|
||||
|
||||
/// The font size of the [BottomNavigationBarItem] labels when they are selected.
|
||||
///
|
||||
/// If [selectedLabelStyle.fontSize] is non-null, it will be used instead of this.
|
||||
///
|
||||
/// Defaults to `14.0`.
|
||||
final double selectedFontSize;
|
||||
|
||||
/// The font size of the [BottomNavigationBarItem] labels when they are not
|
||||
/// selected.
|
||||
///
|
||||
/// If [unselectedLabelStyle.fontSize] is non-null, it will be used instead of this.
|
||||
///
|
||||
/// Defaults to `12.0`.
|
||||
final double unselectedFontSize;
|
||||
|
||||
|
@ -314,8 +363,10 @@ class _BottomNavigationTile extends StatelessWidget {
|
|||
this.colorTween,
|
||||
this.flex,
|
||||
this.selected = false,
|
||||
@required this.selectedFontSize,
|
||||
@required this.unselectedFontSize,
|
||||
@required this.selectedLabelStyle,
|
||||
@required this.unselectedLabelStyle,
|
||||
@required this.selectedIconTheme,
|
||||
@required this.unselectedIconTheme,
|
||||
this.showSelectedLabels,
|
||||
this.showUnselectedLabels,
|
||||
this.indexLabel,
|
||||
|
@ -323,8 +374,8 @@ class _BottomNavigationTile extends StatelessWidget {
|
|||
assert(item != null),
|
||||
assert(animation != null),
|
||||
assert(selected != null),
|
||||
assert(selectedFontSize != null && selectedFontSize >= 0),
|
||||
assert(unselectedFontSize != null && unselectedFontSize >= 0);
|
||||
assert(selectedLabelStyle != null),
|
||||
assert(unselectedLabelStyle != null);
|
||||
|
||||
final BottomNavigationBarType type;
|
||||
final BottomNavigationBarItem item;
|
||||
|
@ -334,8 +385,10 @@ class _BottomNavigationTile extends StatelessWidget {
|
|||
final ColorTween colorTween;
|
||||
final double flex;
|
||||
final bool selected;
|
||||
final double selectedFontSize;
|
||||
final double unselectedFontSize;
|
||||
final IconThemeData selectedIconTheme;
|
||||
final IconThemeData unselectedIconTheme;
|
||||
final TextStyle selectedLabelStyle;
|
||||
final TextStyle unselectedLabelStyle;
|
||||
final String indexLabel;
|
||||
final bool showSelectedLabels;
|
||||
final bool showUnselectedLabels;
|
||||
|
@ -348,41 +401,63 @@ class _BottomNavigationTile extends StatelessWidget {
|
|||
// (which is an integer) by a large number.
|
||||
int size;
|
||||
|
||||
double bottomPadding = selectedFontSize / 2.0;
|
||||
double topPadding = selectedFontSize / 2.0;
|
||||
final double selectedFontSize = selectedLabelStyle.fontSize;
|
||||
|
||||
final double selectedIconSize = selectedIconTheme?.size ?? iconSize;
|
||||
final double unselectedIconSize = unselectedIconTheme?.size ?? iconSize;
|
||||
// The amount that the selected icon is bigger than the unselected icons,
|
||||
// (or zero if the selected icon is not bigger than the unselected icons).
|
||||
final double selectedIconDiff = math.max(selectedIconSize - unselectedIconSize, 0);
|
||||
// The amount that the unselected icons are bigger than the selected icon,
|
||||
// (or zero if the unselected icons are not any bigger than the selected icon).
|
||||
final double unselectedIconDiff = math.max(unselectedIconSize - selectedIconSize, 0);
|
||||
|
||||
// Defines the padding for the animating icons + labels.
|
||||
//
|
||||
// The animations go from "Unselected":
|
||||
// =======
|
||||
// | <-- Padding equal to the text height.
|
||||
// | <-- Padding equal to the text height + 1/2 selectedIconDiff.
|
||||
// | ☆
|
||||
// | text <-- Invisible text.
|
||||
// | text <-- Invisible text + padding equal to 1/2 selectedIconDiff.
|
||||
// =======
|
||||
//
|
||||
// To "Selected":
|
||||
//
|
||||
// =======
|
||||
// | <-- Padding equal to 1/2 text height.
|
||||
// | <-- Padding equal to 1/2 text height + 1/2 unselectedIconDiff.
|
||||
// | ☆
|
||||
// | text
|
||||
// | <-- Padding equal to 1/2 text height.
|
||||
// | <-- Padding equal to 1/2 text height + 1/2 unselectedIconDiff.
|
||||
// =======
|
||||
double bottomPadding;
|
||||
double topPadding;
|
||||
if (showSelectedLabels && !showUnselectedLabels) {
|
||||
bottomPadding = Tween<double>(
|
||||
begin: 0.0,
|
||||
end: selectedFontSize / 2.0,
|
||||
begin: selectedIconDiff / 2.0,
|
||||
end: selectedFontSize / 2.0 - unselectedIconDiff / 2.0,
|
||||
).evaluate(animation);
|
||||
topPadding = Tween<double>(
|
||||
begin: selectedFontSize,
|
||||
end: selectedFontSize / 2.0,
|
||||
begin: selectedFontSize + selectedIconDiff / 2.0,
|
||||
end: selectedFontSize / 2.0 - unselectedIconDiff / 2.0,
|
||||
).evaluate(animation);
|
||||
} else if (!showSelectedLabels && !showUnselectedLabels) {
|
||||
bottomPadding = Tween<double>(
|
||||
begin: selectedIconDiff / 2.0,
|
||||
end: unselectedIconDiff / 2.0,
|
||||
).evaluate(animation);
|
||||
topPadding = Tween<double>(
|
||||
begin: selectedFontSize + selectedIconDiff / 2.0,
|
||||
end: selectedFontSize + unselectedIconDiff / 2.0,
|
||||
).evaluate(animation);
|
||||
} else {
|
||||
bottomPadding = Tween<double>(
|
||||
begin: selectedFontSize / 2.0 + selectedIconDiff / 2.0,
|
||||
end: selectedFontSize / 2.0 + unselectedIconDiff / 2.0,
|
||||
).evaluate(animation);
|
||||
topPadding = Tween<double>(
|
||||
begin: selectedFontSize / 2.0 + selectedIconDiff / 2.0,
|
||||
end: selectedFontSize / 2.0 + unselectedIconDiff / 2.0,
|
||||
).evaluate(animation);
|
||||
}
|
||||
|
||||
// Center all icons if no labels are shown.
|
||||
if (!showSelectedLabels && !showUnselectedLabels) {
|
||||
bottomPadding = 0.0;
|
||||
topPadding = selectedFontSize;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
|
@ -417,13 +492,15 @@ class _BottomNavigationTile extends StatelessWidget {
|
|||
iconSize: iconSize,
|
||||
selected: selected,
|
||||
item: item,
|
||||
selectedIconTheme: selectedIconTheme,
|
||||
unselectedIconTheme: unselectedIconTheme,
|
||||
),
|
||||
_Label(
|
||||
colorTween: colorTween,
|
||||
animation: animation,
|
||||
item: item,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
selectedLabelStyle: selectedLabelStyle,
|
||||
unselectedLabelStyle: unselectedLabelStyle,
|
||||
showSelectedLabels: showSelectedLabels,
|
||||
showUnselectedLabels: showUnselectedLabels,
|
||||
),
|
||||
|
@ -450,6 +527,8 @@ class _TileIcon extends StatelessWidget {
|
|||
@required this.iconSize,
|
||||
@required this.selected,
|
||||
@required this.item,
|
||||
@required this.selectedIconTheme,
|
||||
@required this.unselectedIconTheme,
|
||||
}) : assert(selected != null),
|
||||
assert(item != null),
|
||||
super(key: key);
|
||||
|
@ -459,19 +538,28 @@ class _TileIcon extends StatelessWidget {
|
|||
final double iconSize;
|
||||
final bool selected;
|
||||
final BottomNavigationBarItem item;
|
||||
final IconThemeData selectedIconTheme;
|
||||
final IconThemeData unselectedIconTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Color iconColor = colorTween.evaluate(animation);
|
||||
final IconThemeData defaultIconTheme = IconThemeData(
|
||||
color: iconColor,
|
||||
size: iconSize,
|
||||
);
|
||||
final IconThemeData iconThemeData = IconThemeData.lerp(
|
||||
defaultIconTheme.merge(unselectedIconTheme),
|
||||
defaultIconTheme.merge(selectedIconTheme),
|
||||
animation.value,
|
||||
);
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.topCenter,
|
||||
heightFactor: 1.0,
|
||||
child: Container(
|
||||
child: IconTheme(
|
||||
data: IconThemeData(
|
||||
color: iconColor,
|
||||
size: iconSize,
|
||||
),
|
||||
data: iconThemeData,
|
||||
child: selected ? item.activeIcon : item.icon,
|
||||
),
|
||||
),
|
||||
|
@ -485,15 +573,15 @@ class _Label extends StatelessWidget {
|
|||
@required this.colorTween,
|
||||
@required this.animation,
|
||||
@required this.item,
|
||||
@required this.selectedFontSize,
|
||||
@required this.unselectedFontSize,
|
||||
@required this.selectedLabelStyle,
|
||||
@required this.unselectedLabelStyle,
|
||||
@required this.showSelectedLabels,
|
||||
@required this.showUnselectedLabels,
|
||||
}) : assert(colorTween != null),
|
||||
assert(animation != null),
|
||||
assert(item != null),
|
||||
assert(selectedFontSize != null),
|
||||
assert(unselectedFontSize != null),
|
||||
assert(selectedLabelStyle != null),
|
||||
assert(unselectedLabelStyle != null),
|
||||
assert(showSelectedLabels != null),
|
||||
assert(showUnselectedLabels != null),
|
||||
super(key: key);
|
||||
|
@ -501,15 +589,23 @@ class _Label extends StatelessWidget {
|
|||
final ColorTween colorTween;
|
||||
final Animation<double> animation;
|
||||
final BottomNavigationBarItem item;
|
||||
final double selectedFontSize;
|
||||
final double unselectedFontSize;
|
||||
final TextStyle selectedLabelStyle;
|
||||
final TextStyle unselectedLabelStyle;
|
||||
final bool showSelectedLabels;
|
||||
final bool showUnselectedLabels;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final double selectedFontSize = selectedLabelStyle.fontSize;
|
||||
final double unselectedFontSize = unselectedLabelStyle.fontSize;
|
||||
|
||||
final TextStyle customStyle = TextStyle.lerp(
|
||||
unselectedLabelStyle,
|
||||
selectedLabelStyle,
|
||||
animation.value,
|
||||
);
|
||||
Widget text = DefaultTextStyle.merge(
|
||||
style: TextStyle(
|
||||
style: customStyle.copyWith(
|
||||
fontSize: selectedFontSize,
|
||||
color: colorTween.evaluate(animation),
|
||||
),
|
||||
|
@ -677,12 +773,25 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
|
|||
}
|
||||
}
|
||||
|
||||
// If the given [TextStyle] has a non-null `fontSize`, it should be used.
|
||||
// Otherwise, the [selectedFontSize] parameter should be used.
|
||||
static TextStyle _effectiveTextStyle(TextStyle textStyle, double fontSize) {
|
||||
textStyle ??= const TextStyle(inherit: false);
|
||||
// Prefer the font size on textStyle if present.
|
||||
return textStyle.fontSize == null ? textStyle.copyWith(fontSize: fontSize) : textStyle;
|
||||
}
|
||||
|
||||
List<Widget> _createTiles() {
|
||||
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
||||
assert(localizations != null);
|
||||
|
||||
final ThemeData themeData = Theme.of(context);
|
||||
|
||||
final TextStyle effectiveSelectedLabelStyle =
|
||||
_effectiveTextStyle(widget.selectedLabelStyle, widget.selectedFontSize);
|
||||
final TextStyle effectiveUnselectedLabelStyle =
|
||||
_effectiveTextStyle(widget.unselectedLabelStyle, widget.unselectedFontSize);
|
||||
|
||||
Color themeColor;
|
||||
switch (themeData.brightness) {
|
||||
case Brightness.light:
|
||||
|
@ -716,8 +825,10 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
|
|||
widget.items[i],
|
||||
_animations[i],
|
||||
widget.iconSize,
|
||||
selectedFontSize: widget.selectedFontSize,
|
||||
unselectedFontSize: widget.unselectedFontSize,
|
||||
selectedIconTheme: widget.selectedIconTheme,
|
||||
unselectedIconTheme: widget.unselectedIconTheme,
|
||||
selectedLabelStyle: effectiveSelectedLabelStyle,
|
||||
unselectedLabelStyle: effectiveUnselectedLabelStyle,
|
||||
onTap: () {
|
||||
if (widget.onTap != null)
|
||||
widget.onTap(i);
|
||||
|
|
|
@ -71,8 +71,8 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('Fixed BottomNavigationBar defaults', (WidgetTester tester) async {
|
||||
const Color primaryColor = Colors.black;
|
||||
const Color captionColor = Colors.purple;
|
||||
const Color primaryColor = Color(0xFF000001);
|
||||
const Color captionColor = Color(0xFF000002);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
@ -100,19 +100,306 @@ void main() {
|
|||
|
||||
const double selectedFontSize = 14.0;
|
||||
const double unselectedFontSize = 12.0;
|
||||
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.fontSize, selectedFontSize);
|
||||
final TextStyle selectedFontStyle = tester.renderObject<RenderParagraph>(find.text('AC')).text.style;
|
||||
final TextStyle unselectedFontStyle = tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style;
|
||||
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
|
||||
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
|
||||
expect(selectedFontStyle.color, equals(primaryColor));
|
||||
expect(selectedFontStyle.fontSize, selectedFontSize);
|
||||
expect(selectedFontStyle.fontWeight, isNull);
|
||||
expect(selectedFontStyle.height, isNull);
|
||||
expect(unselectedFontStyle.color, equals(captionColor));
|
||||
expect(unselectedFontStyle.fontWeight, isNull);
|
||||
expect(unselectedFontStyle.height, isNull);
|
||||
// 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(selectedIcon.color, equals(primaryColor));
|
||||
expect(selectedIcon.fontSize, equals(24.0));
|
||||
expect(unselectedIcon.color, equals(captionColor));
|
||||
expect(unselectedIcon.fontSize, equals(24.0));
|
||||
expect(_getOpacity(tester, 'Alarm'), equals(1.0));
|
||||
expect(_getMaterial(tester).elevation, equals(8.0));
|
||||
});
|
||||
|
||||
testWidgets('Custom selected and unselected font styles', (WidgetTester tester) async {
|
||||
const TextStyle selectedTextStyle = TextStyle(fontWeight: FontWeight.w200, fontSize: 18.0);
|
||||
const TextStyle unselectedTextStyle = TextStyle(fontWeight: FontWeight.w600, fontSize: 12.0);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedLabelStyle: selectedTextStyle,
|
||||
unselectedLabelStyle: unselectedTextStyle,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final TextStyle selectedFontStyle = tester.renderObject<RenderParagraph>(find.text('AC')).text.style;
|
||||
final TextStyle unselectedFontStyle = tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style;
|
||||
expect(selectedFontStyle.fontSize, equals(selectedTextStyle.fontSize));
|
||||
expect(selectedFontStyle.fontWeight, equals(selectedTextStyle.fontWeight));
|
||||
expect(
|
||||
tester.firstWidget<Transform>(find.ancestor(of: find.text('Alarm'), matching: find.byType(Transform))).transform,
|
||||
equals(Matrix4.diagonal3(Vector3.all(unselectedTextStyle.fontSize / selectedTextStyle.fontSize))),
|
||||
);
|
||||
expect(unselectedFontStyle.fontWeight, equals(unselectedTextStyle.fontWeight));
|
||||
});
|
||||
|
||||
testWidgets('font size on text styles overrides font size params', (WidgetTester tester) async {
|
||||
const TextStyle selectedTextStyle = TextStyle(fontSize: 18.0);
|
||||
const TextStyle unselectedTextStyle = TextStyle(fontSize: 12.0);
|
||||
const double selectedFontSize = 17.0;
|
||||
const double unselectedFontSize = 11.0;
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedLabelStyle: selectedTextStyle,
|
||||
unselectedLabelStyle: unselectedTextStyle,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final TextStyle selectedFontStyle = tester.renderObject<RenderParagraph>(find.text('AC')).text.style;
|
||||
expect(selectedFontStyle.fontSize, equals(selectedTextStyle.fontSize));
|
||||
expect(
|
||||
tester.firstWidget<Transform>(find.ancestor(of: find.text('Alarm'), matching: find.byType(Transform))).transform,
|
||||
equals(Matrix4.diagonal3(Vector3.all(unselectedTextStyle.fontSize / selectedTextStyle.fontSize))),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('Custom selected and unselected icon themes', (WidgetTester tester) async {
|
||||
const IconThemeData selectedIconTheme = IconThemeData(size: 36, color: Color(1));
|
||||
const IconThemeData unselectedIconTheme = IconThemeData(size: 18, color: Color(2));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedIconTheme: selectedIconTheme,
|
||||
unselectedIconTheme: unselectedIconTheme,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
|
||||
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
|
||||
expect(selectedIcon.color, equals(selectedIconTheme.color));
|
||||
expect(selectedIcon.fontSize, equals(selectedIconTheme.size));
|
||||
expect(unselectedIcon.color, equals(unselectedIconTheme.color));
|
||||
expect(unselectedIcon.fontSize, equals(unselectedIconTheme.size));
|
||||
});
|
||||
|
||||
testWidgets('color on icon theme overrides selected and unselected item colors', (WidgetTester tester) async {
|
||||
const IconThemeData selectedIconTheme = IconThemeData(size: 36, color: Color(1));
|
||||
const IconThemeData unselectedIconTheme = IconThemeData(size: 18, color: Color(2));
|
||||
const Color selectedItemColor = Color(3);
|
||||
const Color unselectedItemColor = Color(4);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
selectedIconTheme: selectedIconTheme,
|
||||
unselectedIconTheme: unselectedIconTheme,
|
||||
selectedItemColor: selectedItemColor,
|
||||
unselectedItemColor: unselectedItemColor,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final TextStyle selectedFontStyle = tester.renderObject<RenderParagraph>(find.text('AC')).text.style;
|
||||
final TextStyle unselectedFontStyle = tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style;
|
||||
final TextStyle selectedIcon = _iconStyle(tester, Icons.ac_unit);
|
||||
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
|
||||
expect(selectedIcon.color, equals(selectedIconTheme.color));
|
||||
expect(unselectedIcon.color, equals(unselectedIconTheme.color));
|
||||
expect(selectedFontStyle.color, equals(selectedItemColor));
|
||||
expect(unselectedFontStyle.color, equals(unselectedItemColor));
|
||||
});
|
||||
|
||||
testWidgets('Padding is calculated properly on items - all labels', (WidgetTester tester) async {
|
||||
const double selectedFontSize = 16.0;
|
||||
const double unselectedFontSize = 12.0;
|
||||
const double selectedIconSize = 36.0;
|
||||
const double unselectedIconSize = 20.0;
|
||||
const IconThemeData selectedIconTheme = IconThemeData(size: selectedIconSize);
|
||||
const IconThemeData unselectedIconTheme = IconThemeData(size: unselectedIconSize);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: true,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
selectedIconTheme: selectedIconTheme,
|
||||
unselectedIconTheme: unselectedIconTheme,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final EdgeInsets selectedItemPadding = _itemPadding(tester, Icons.ac_unit);
|
||||
expect(selectedItemPadding.top, equals(selectedFontSize / 2.0));
|
||||
expect(selectedItemPadding.bottom, equals(selectedFontSize / 2.0));
|
||||
final EdgeInsets unselectedItemPadding = _itemPadding(tester, Icons.access_alarm);
|
||||
const double expectedUnselectedPadding = (selectedIconSize - unselectedIconSize) / 2.0 + selectedFontSize / 2.0;
|
||||
expect(unselectedItemPadding.top, equals(expectedUnselectedPadding));
|
||||
expect(unselectedItemPadding.bottom, equals(expectedUnselectedPadding));
|
||||
});
|
||||
|
||||
testWidgets('Padding is calculated properly on items - selected labels only', (WidgetTester tester) async {
|
||||
const double selectedFontSize = 16.0;
|
||||
const double unselectedFontSize = 12.0;
|
||||
const double selectedIconSize = 36.0;
|
||||
const double unselectedIconSize = 20.0;
|
||||
const IconThemeData selectedIconTheme = IconThemeData(size: selectedIconSize);
|
||||
const IconThemeData unselectedIconTheme = IconThemeData(size: unselectedIconSize);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
showSelectedLabels: true,
|
||||
showUnselectedLabels: false,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
selectedIconTheme: selectedIconTheme,
|
||||
unselectedIconTheme: unselectedIconTheme,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final EdgeInsets selectedItemPadding = _itemPadding(tester, Icons.ac_unit);
|
||||
expect(selectedItemPadding.top, equals(selectedFontSize / 2.0));
|
||||
expect(selectedItemPadding.bottom, equals(selectedFontSize / 2.0));
|
||||
final EdgeInsets unselectedItemPadding = _itemPadding(tester, Icons.access_alarm);
|
||||
expect(unselectedItemPadding.top, equals((selectedIconSize - unselectedIconSize) / 2.0 + selectedFontSize));
|
||||
expect(unselectedItemPadding.bottom, equals((selectedIconSize - unselectedIconSize) / 2.0));
|
||||
});
|
||||
|
||||
testWidgets('Padding is calculated properly on items - no labels', (WidgetTester tester) async {
|
||||
const double selectedFontSize = 16.0;
|
||||
const double unselectedFontSize = 12.0;
|
||||
const double selectedIconSize = 36.0;
|
||||
const double unselectedIconSize = 20.0;
|
||||
const IconThemeData selectedIconTheme = IconThemeData(size: selectedIconSize);
|
||||
const IconThemeData unselectedIconTheme = IconThemeData(size: unselectedIconSize);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
bottomNavigationBar: BottomNavigationBar(
|
||||
type: BottomNavigationBarType.fixed,
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
selectedFontSize: selectedFontSize,
|
||||
unselectedFontSize: unselectedFontSize,
|
||||
selectedIconTheme: selectedIconTheme,
|
||||
unselectedIconTheme: unselectedIconTheme,
|
||||
items: const <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.ac_unit),
|
||||
title: Text('AC'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.access_alarm),
|
||||
title: Text('Alarm'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
final EdgeInsets selectedItemPadding = _itemPadding(tester, Icons.ac_unit);
|
||||
expect(selectedItemPadding.top, equals(selectedFontSize));
|
||||
expect(selectedItemPadding.bottom, equals(0.0));
|
||||
final EdgeInsets unselectedItemPadding = _itemPadding(tester, Icons.access_alarm);
|
||||
expect(unselectedItemPadding.top, equals((selectedIconSize - unselectedIconSize) / 2.0 + selectedFontSize));
|
||||
expect(unselectedItemPadding.bottom, equals((selectedIconSize - unselectedIconSize) / 2.0));
|
||||
});
|
||||
|
||||
testWidgets('Shifting BottomNavigationBar defaults', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
|
@ -1399,3 +1686,19 @@ Material _getMaterial(WidgetTester tester) {
|
|||
find.descendant(of: find.byType(BottomNavigationBar), matching: find.byType(Material)),
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle _iconStyle(WidgetTester tester, IconData icon) {
|
||||
final RichText iconRichText = tester.widget<RichText>(
|
||||
find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)),
|
||||
);
|
||||
return iconRichText.text.style;
|
||||
}
|
||||
|
||||
EdgeInsets _itemPadding(WidgetTester tester, IconData icon) {
|
||||
return tester.widget<Padding>(
|
||||
find.descendant(
|
||||
of: find.ancestor(of: find.byIcon(icon), matching: find.byType(InkResponse)),
|
||||
matching: find.byType(Padding)
|
||||
).first,
|
||||
).padding.resolve(TextDirection.ltr);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue