Unverified Commit a40e5c90 authored by MH Johnson's avatar MH Johnson Committed by GitHub

[Material] selected/unselected label styles + icon themes on BottomNavigationBar (#31018)

* add text style params

* add icon theme params

* Added tests 
parent 1d91bd25
...@@ -189,7 +189,6 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo> ...@@ -189,7 +189,6 @@ class _BottomNavigationDemoState extends State<BottomNavigationDemo>
.toList(), .toList(),
currentIndex: _currentIndex, currentIndex: _currentIndex,
type: _type, type: _type,
//iconSize: 4.0,
onTap: (int index) { onTap: (int index) {
setState(() { setState(() {
_navigationViews[_currentIndex].controller.reverse(); _navigationViews[_currentIndex].controller.reverse();
......
...@@ -147,6 +147,17 @@ class BottomNavigationBar extends StatefulWidget { ...@@ -147,6 +147,17 @@ class BottomNavigationBar extends StatefulWidget {
/// The [iconSize], [selectedFontSize], [unselectedFontSize], and [elevation] /// The [iconSize], [selectedFontSize], [unselectedFontSize], and [elevation]
/// arguments must be non-null and non-negative. /// 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 /// Only one of [selectedItemColor] and [fixedColor] can be specified. The
/// former is preferred, [fixedColor] only exists for the sake of /// former is preferred, [fixedColor] only exists for the sake of
/// backwards compatibility. /// backwards compatibility.
...@@ -168,8 +179,12 @@ class BottomNavigationBar extends StatefulWidget { ...@@ -168,8 +179,12 @@ class BottomNavigationBar extends StatefulWidget {
this.iconSize = 24.0, this.iconSize = 24.0,
Color selectedItemColor, Color selectedItemColor,
this.unselectedItemColor, this.unselectedItemColor,
this.selectedIconTheme = const IconThemeData(),
this.unselectedIconTheme = const IconThemeData(),
this.selectedFontSize = 14.0, this.selectedFontSize = 14.0,
this.unselectedFontSize = 12.0, this.unselectedFontSize = 12.0,
this.selectedLabelStyle,
this.unselectedLabelStyle,
this.showSelectedLabels = true, this.showSelectedLabels = true,
bool showUnselectedLabels, bool showUnselectedLabels,
}) : assert(items != null), }) : assert(items != null),
...@@ -250,14 +265,48 @@ class BottomNavigationBar extends StatefulWidget { ...@@ -250,14 +265,48 @@ class BottomNavigationBar extends StatefulWidget {
/// If null then the [TextTheme.caption]'s color is used. /// If null then the [TextTheme.caption]'s color is used.
final Color unselectedItemColor; 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. /// 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`. /// Defaults to `14.0`.
final double selectedFontSize; final double selectedFontSize;
/// The font size of the [BottomNavigationBarItem] labels when they are not /// The font size of the [BottomNavigationBarItem] labels when they are not
/// selected. /// selected.
/// ///
/// If [unselectedLabelStyle.fontSize] is non-null, it will be used instead of this.
///
/// Defaults to `12.0`. /// Defaults to `12.0`.
final double unselectedFontSize; final double unselectedFontSize;
...@@ -314,8 +363,10 @@ class _BottomNavigationTile extends StatelessWidget { ...@@ -314,8 +363,10 @@ class _BottomNavigationTile extends StatelessWidget {
this.colorTween, this.colorTween,
this.flex, this.flex,
this.selected = false, this.selected = false,
@required this.selectedFontSize, @required this.selectedLabelStyle,
@required this.unselectedFontSize, @required this.unselectedLabelStyle,
@required this.selectedIconTheme,
@required this.unselectedIconTheme,
this.showSelectedLabels, this.showSelectedLabels,
this.showUnselectedLabels, this.showUnselectedLabels,
this.indexLabel, this.indexLabel,
...@@ -323,8 +374,8 @@ class _BottomNavigationTile extends StatelessWidget { ...@@ -323,8 +374,8 @@ class _BottomNavigationTile extends StatelessWidget {
assert(item != null), assert(item != null),
assert(animation != null), assert(animation != null),
assert(selected != null), assert(selected != null),
assert(selectedFontSize != null && selectedFontSize >= 0), assert(selectedLabelStyle != null),
assert(unselectedFontSize != null && unselectedFontSize >= 0); assert(unselectedLabelStyle != null);
final BottomNavigationBarType type; final BottomNavigationBarType type;
final BottomNavigationBarItem item; final BottomNavigationBarItem item;
...@@ -334,8 +385,10 @@ class _BottomNavigationTile extends StatelessWidget { ...@@ -334,8 +385,10 @@ class _BottomNavigationTile extends StatelessWidget {
final ColorTween colorTween; final ColorTween colorTween;
final double flex; final double flex;
final bool selected; final bool selected;
final double selectedFontSize; final IconThemeData selectedIconTheme;
final double unselectedFontSize; final IconThemeData unselectedIconTheme;
final TextStyle selectedLabelStyle;
final TextStyle unselectedLabelStyle;
final String indexLabel; final String indexLabel;
final bool showSelectedLabels; final bool showSelectedLabels;
final bool showUnselectedLabels; final bool showUnselectedLabels;
...@@ -348,41 +401,63 @@ class _BottomNavigationTile extends StatelessWidget { ...@@ -348,41 +401,63 @@ class _BottomNavigationTile extends StatelessWidget {
// (which is an integer) by a large number. // (which is an integer) by a large number.
int size; int size;
double bottomPadding = selectedFontSize / 2.0; final double selectedFontSize = selectedLabelStyle.fontSize;
double topPadding = selectedFontSize / 2.0;
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. // Defines the padding for the animating icons + labels.
// //
// The animations go from "Unselected": // 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": // To "Selected":
// //
// ======= // =======
// | <-- Padding equal to 1/2 text height. // | <-- Padding equal to 1/2 text height + 1/2 unselectedIconDiff.
// | ☆ // | ☆
// | text // | 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) { if (showSelectedLabels && !showUnselectedLabels) {
bottomPadding = Tween<double>( bottomPadding = Tween<double>(
begin: 0.0, begin: selectedIconDiff / 2.0,
end: selectedFontSize / 2.0, end: selectedFontSize / 2.0 - unselectedIconDiff / 2.0,
).evaluate(animation); ).evaluate(animation);
topPadding = Tween<double>( topPadding = Tween<double>(
begin: selectedFontSize, begin: selectedFontSize + selectedIconDiff / 2.0,
end: selectedFontSize / 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); ).evaluate(animation);
}
// Center all icons if no labels are shown.
if (!showSelectedLabels && !showUnselectedLabels) {
bottomPadding = 0.0;
topPadding = selectedFontSize;
} }
switch (type) { switch (type) {
...@@ -417,13 +492,15 @@ class _BottomNavigationTile extends StatelessWidget { ...@@ -417,13 +492,15 @@ class _BottomNavigationTile extends StatelessWidget {
iconSize: iconSize, iconSize: iconSize,
selected: selected, selected: selected,
item: item, item: item,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
), ),
_Label( _Label(
colorTween: colorTween, colorTween: colorTween,
animation: animation, animation: animation,
item: item, item: item,
selectedFontSize: selectedFontSize, selectedLabelStyle: selectedLabelStyle,
unselectedFontSize: unselectedFontSize, unselectedLabelStyle: unselectedLabelStyle,
showSelectedLabels: showSelectedLabels, showSelectedLabels: showSelectedLabels,
showUnselectedLabels: showUnselectedLabels, showUnselectedLabels: showUnselectedLabels,
), ),
...@@ -450,6 +527,8 @@ class _TileIcon extends StatelessWidget { ...@@ -450,6 +527,8 @@ class _TileIcon extends StatelessWidget {
@required this.iconSize, @required this.iconSize,
@required this.selected, @required this.selected,
@required this.item, @required this.item,
@required this.selectedIconTheme,
@required this.unselectedIconTheme,
}) : assert(selected != null), }) : assert(selected != null),
assert(item != null), assert(item != null),
super(key: key); super(key: key);
...@@ -459,19 +538,28 @@ class _TileIcon extends StatelessWidget { ...@@ -459,19 +538,28 @@ class _TileIcon extends StatelessWidget {
final double iconSize; final double iconSize;
final bool selected; final bool selected;
final BottomNavigationBarItem item; final BottomNavigationBarItem item;
final IconThemeData selectedIconTheme;
final IconThemeData unselectedIconTheme;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final Color iconColor = colorTween.evaluate(animation); 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( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
heightFactor: 1.0, heightFactor: 1.0,
child: Container( child: Container(
child: IconTheme( child: IconTheme(
data: IconThemeData( data: iconThemeData,
color: iconColor,
size: iconSize,
),
child: selected ? item.activeIcon : item.icon, child: selected ? item.activeIcon : item.icon,
), ),
), ),
...@@ -485,15 +573,15 @@ class _Label extends StatelessWidget { ...@@ -485,15 +573,15 @@ class _Label extends StatelessWidget {
@required this.colorTween, @required this.colorTween,
@required this.animation, @required this.animation,
@required this.item, @required this.item,
@required this.selectedFontSize, @required this.selectedLabelStyle,
@required this.unselectedFontSize, @required this.unselectedLabelStyle,
@required this.showSelectedLabels, @required this.showSelectedLabels,
@required this.showUnselectedLabels, @required this.showUnselectedLabels,
}) : assert(colorTween != null), }) : assert(colorTween != null),
assert(animation != null), assert(animation != null),
assert(item != null), assert(item != null),
assert(selectedFontSize != null), assert(selectedLabelStyle != null),
assert(unselectedFontSize != null), assert(unselectedLabelStyle != null),
assert(showSelectedLabels != null), assert(showSelectedLabels != null),
assert(showUnselectedLabels != null), assert(showUnselectedLabels != null),
super(key: key); super(key: key);
...@@ -501,15 +589,23 @@ class _Label extends StatelessWidget { ...@@ -501,15 +589,23 @@ class _Label extends StatelessWidget {
final ColorTween colorTween; final ColorTween colorTween;
final Animation<double> animation; final Animation<double> animation;
final BottomNavigationBarItem item; final BottomNavigationBarItem item;
final double selectedFontSize; final TextStyle selectedLabelStyle;
final double unselectedFontSize; final TextStyle unselectedLabelStyle;
final bool showSelectedLabels; final bool showSelectedLabels;
final bool showUnselectedLabels; final bool showUnselectedLabels;
@override @override
Widget build(BuildContext context) { 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( Widget text = DefaultTextStyle.merge(
style: TextStyle( style: customStyle.copyWith(
fontSize: selectedFontSize, fontSize: selectedFontSize,
color: colorTween.evaluate(animation), color: colorTween.evaluate(animation),
), ),
...@@ -677,12 +773,25 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr ...@@ -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() { List<Widget> _createTiles() {
final MaterialLocalizations localizations = MaterialLocalizations.of(context); final MaterialLocalizations localizations = MaterialLocalizations.of(context);
assert(localizations != null); assert(localizations != null);
final ThemeData themeData = Theme.of(context); final ThemeData themeData = Theme.of(context);
final TextStyle effectiveSelectedLabelStyle =
_effectiveTextStyle(widget.selectedLabelStyle, widget.selectedFontSize);
final TextStyle effectiveUnselectedLabelStyle =
_effectiveTextStyle(widget.unselectedLabelStyle, widget.unselectedFontSize);
Color themeColor; Color themeColor;
switch (themeData.brightness) { switch (themeData.brightness) {
case Brightness.light: case Brightness.light:
...@@ -716,8 +825,10 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr ...@@ -716,8 +825,10 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
widget.items[i], widget.items[i],
_animations[i], _animations[i],
widget.iconSize, widget.iconSize,
selectedFontSize: widget.selectedFontSize, selectedIconTheme: widget.selectedIconTheme,
unselectedFontSize: widget.unselectedFontSize, unselectedIconTheme: widget.unselectedIconTheme,
selectedLabelStyle: effectiveSelectedLabelStyle,
unselectedLabelStyle: effectiveUnselectedLabelStyle,
onTap: () { onTap: () {
if (widget.onTap != null) if (widget.onTap != null)
widget.onTap(i); widget.onTap(i);
......
...@@ -71,8 +71,8 @@ void main() { ...@@ -71,8 +71,8 @@ void main() {
}); });
testWidgets('Fixed BottomNavigationBar defaults', (WidgetTester tester) async { testWidgets('Fixed BottomNavigationBar defaults', (WidgetTester tester) async {
const Color primaryColor = Colors.black; const Color primaryColor = Color(0xFF000001);
const Color captionColor = Colors.purple; const Color captionColor = Color(0xFF000002);
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -100,19 +100,306 @@ void main() { ...@@ -100,19 +100,306 @@ void main() {
const double selectedFontSize = 14.0; const double selectedFontSize = 14.0;
const double unselectedFontSize = 12.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. // 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( expect(
tester.firstWidget<Transform>(find.ancestor(of: find.text('Alarm'), matching: find.byType(Transform))).transform, tester.firstWidget<Transform>(find.ancestor(of: find.text('Alarm'), matching: find.byType(Transform))).transform,
equals(Matrix4.diagonal3(Vector3.all(unselectedFontSize / selectedFontSize))), equals(Matrix4.diagonal3(Vector3.all(unselectedFontSize / selectedFontSize))),
); );
expect(tester.renderObject<RenderParagraph>(find.text('AC')).text.style.color, equals(primaryColor)); expect(selectedIcon.color, equals(primaryColor));
expect(tester.renderObject<RenderParagraph>(find.text('Alarm')).text.style.color, equals(captionColor)); 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(_getOpacity(tester, 'Alarm'), equals(1.0));
expect(_getMaterial(tester).elevation, equals(8.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 { testWidgets('Shifting BottomNavigationBar defaults', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -1399,3 +1686,19 @@ Material _getMaterial(WidgetTester tester) { ...@@ -1399,3 +1686,19 @@ Material _getMaterial(WidgetTester tester) {
find.descendant(of: find.byType(BottomNavigationBar), matching: find.byType(Material)), 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);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment