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

[Material] Added BottomNavigationBarTheme (#54714)

parent f050ee67
......@@ -28,6 +28,7 @@ export 'src/material/banner_theme.dart';
export 'src/material/bottom_app_bar.dart';
export 'src/material/bottom_app_bar_theme.dart';
export 'src/material/bottom_navigation_bar.dart';
export 'src/material/bottom_navigation_bar_theme.dart';
export 'src/material/bottom_sheet.dart';
export 'src/material/bottom_sheet_theme.dart';
export 'src/material/button.dart';
......
......@@ -8,7 +8,7 @@ import 'dart:math' as math;
import 'package:flutter/widgets.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
import 'colors.dart';
import 'bottom_navigation_bar_theme.dart';
import 'constants.dart';
import 'debug.dart';
import 'ink_well.dart';
......@@ -172,21 +172,21 @@ class BottomNavigationBar extends StatefulWidget {
@required this.items,
this.onTap,
this.currentIndex = 0,
this.elevation = 8.0,
BottomNavigationBarType type,
this.elevation,
this.type,
Color fixedColor,
this.backgroundColor,
this.iconSize = 24.0,
Color selectedItemColor,
this.unselectedItemColor,
this.selectedIconTheme = const IconThemeData(),
this.unselectedIconTheme = const IconThemeData(),
this.selectedIconTheme,
this.unselectedIconTheme,
this.selectedFontSize = 14.0,
this.unselectedFontSize = 12.0,
this.selectedLabelStyle,
this.unselectedLabelStyle,
this.showSelectedLabels = true,
bool showUnselectedLabels,
this.showUnselectedLabels,
}) : assert(items != null),
assert(items.length >= 2),
assert(
......@@ -194,7 +194,7 @@ class BottomNavigationBar extends StatefulWidget {
'Every item must have a non-null title',
),
assert(0 <= currentIndex && currentIndex < items.length),
assert(elevation != null && elevation >= 0.0),
assert(elevation == null || elevation >= 0.0),
assert(iconSize != null && iconSize >= 0.0),
assert(
selectedItemColor == null || fixedColor == null,
......@@ -203,9 +203,7 @@ class BottomNavigationBar extends StatefulWidget {
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);
/// Defines the appearance of the button items that are arrayed within the
......@@ -316,37 +314,6 @@ class BottomNavigationBar extends StatefulWidget {
/// 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();
}
......@@ -401,10 +368,17 @@ class _BottomNavigationTile extends StatelessWidget {
// (which is an integer) by a large number.
int size;
final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
final double selectedFontSize = selectedLabelStyle.fontSize;
final double selectedIconSize = selectedIconTheme?.size ?? iconSize;
final double unselectedIconSize = unselectedIconTheme?.size ?? iconSize;
final double selectedIconSize = selectedIconTheme?.size
?? bottomTheme?.selectedIconTheme?.size
?? iconSize;
final double unselectedIconSize = unselectedIconTheme?.size
?? bottomTheme?.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);
......@@ -491,17 +465,17 @@ class _BottomNavigationTile extends StatelessWidget {
iconSize: iconSize,
selected: selected,
item: item,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
selectedIconTheme: selectedIconTheme ?? bottomTheme.selectedIconTheme,
unselectedIconTheme: unselectedIconTheme ?? bottomTheme.unselectedIconTheme,
),
_Label(
colorTween: colorTween,
animation: animation,
item: item,
selectedLabelStyle: selectedLabelStyle,
unselectedLabelStyle: unselectedLabelStyle,
showSelectedLabels: showSelectedLabels,
showUnselectedLabels: showUnselectedLabels,
selectedLabelStyle: selectedLabelStyle ?? bottomTheme.selectedLabelStyle,
unselectedLabelStyle: unselectedLabelStyle ?? bottomTheme.unselectedLabelStyle,
showSelectedLabels: showSelectedLabels ?? bottomTheme.showUnselectedLabels,
showUnselectedLabels: showUnselectedLabels ?? bottomTheme.showUnselectedLabels,
),
],
),
......@@ -693,6 +667,33 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
_backgroundColor = widget.items[widget.currentIndex].backgroundColor;
}
// Computes the default value for the [type] parameter.
//
// If type is provided, it is returned. Next, if the bottom navigation bar
// theme provides a type, it is used. Finally, the default behavior will be
// [BottomNavigationBarType.fixed] for 3 or fewer items, and
// [BottomNavigationBarType.shifting] is used for 4+ items.
BottomNavigationBarType get _effectiveType {
return widget.type
?? BottomNavigationBarTheme.of(context).type
?? (widget.items.length <= 3 ? BottomNavigationBarType.fixed : BottomNavigationBarType.shifting);
}
// Computes the default value for the [showUnselected] parameter.
//
// Unselected labels are shown by default for [BottomNavigationBarType.fixed],
// and hidden by default for [BottomNavigationBarType.shifting].
bool get _defaultShowUnselected {
switch (_effectiveType) {
case BottomNavigationBarType.shifting:
return false;
case BottomNavigationBarType.fixed:
return true;
}
assert(false);
return false;
}
@override
void initState() {
super.initState();
......@@ -757,7 +758,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
}
if (widget.currentIndex != oldWidget.currentIndex) {
switch (widget.type) {
switch (_effectiveType) {
case BottomNavigationBarType.fixed:
break;
case BottomNavigationBarType.shifting:
......@@ -785,11 +786,18 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
assert(localizations != null);
final ThemeData themeData = Theme.of(context);
final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
final TextStyle effectiveSelectedLabelStyle =
_effectiveTextStyle(widget.selectedLabelStyle, widget.selectedFontSize);
_effectiveTextStyle(
widget.selectedLabelStyle ?? bottomTheme.selectedLabelStyle,
widget.selectedFontSize,
);
final TextStyle effectiveUnselectedLabelStyle =
_effectiveTextStyle(widget.unselectedLabelStyle, widget.unselectedFontSize);
_effectiveTextStyle(
widget.unselectedLabelStyle ?? bottomTheme.unselectedLabelStyle,
widget.unselectedFontSize,
);
Color themeColor;
switch (themeData.brightness) {
......@@ -802,17 +810,26 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
}
ColorTween colorTween;
switch (widget.type) {
switch (_effectiveType) {
case BottomNavigationBarType.fixed:
colorTween = ColorTween(
begin: widget.unselectedItemColor ?? themeData.textTheme.caption.color,
end: widget.selectedItemColor ?? widget.fixedColor ?? themeColor,
begin: widget.unselectedItemColor
?? bottomTheme.unselectedItemColor
?? themeData.textTheme.caption.color,
end: widget.selectedItemColor
?? bottomTheme.selectedItemColor
?? widget.fixedColor
?? themeColor,
);
break;
case BottomNavigationBarType.shifting:
colorTween = ColorTween(
begin: widget.unselectedItemColor ?? Colors.white,
end: widget.selectedItemColor ?? Colors.white,
begin: widget.unselectedItemColor
?? bottomTheme.unselectedItemColor
?? themeData.colorScheme.surface,
end: widget.selectedItemColor
?? bottomTheme.selectedItemColor
?? themeData.colorScheme.surface,
);
break;
}
......@@ -820,12 +837,12 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
final List<Widget> tiles = <Widget>[];
for (int i = 0; i < widget.items.length; i++) {
tiles.add(_BottomNavigationTile(
widget.type,
_effectiveType,
widget.items[i],
_animations[i],
widget.iconSize,
selectedIconTheme: widget.selectedIconTheme,
unselectedIconTheme: widget.unselectedIconTheme,
selectedIconTheme: widget.selectedIconTheme ?? bottomTheme.selectedIconTheme,
unselectedIconTheme: widget.unselectedIconTheme ?? bottomTheme.unselectedIconTheme,
selectedLabelStyle: effectiveSelectedLabelStyle,
unselectedLabelStyle: effectiveUnselectedLabelStyle,
onTap: () {
......@@ -835,8 +852,8 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
colorTween: colorTween,
flex: _evaluateFlex(_animations[i]),
selected: i == widget.currentIndex,
showSelectedLabels: widget.showSelectedLabels,
showUnselectedLabels: widget.showUnselectedLabels,
showSelectedLabels: widget.showSelectedLabels ?? bottomTheme.showSelectedLabels,
showUnselectedLabels: widget.showUnselectedLabels ?? bottomTheme.showUnselectedLabels ?? _defaultShowUnselected,
indexLabel: localizations.tabLabel(tabIndex: i + 1, tabCount: widget.items.length),
));
}
......@@ -859,12 +876,14 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
assert(debugCheckHasMaterialLocalizations(context));
assert(debugCheckHasMediaQuery(context));
final BottomNavigationBarThemeData bottomTheme = BottomNavigationBarTheme.of(context);
// Labels apply up to _bottomMargin padding. Remainder is media padding.
final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - widget.selectedFontSize / 2.0, 0.0);
Color backgroundColor;
switch (widget.type) {
switch (_effectiveType) {
case BottomNavigationBarType.fixed:
backgroundColor = widget.backgroundColor;
backgroundColor = widget.backgroundColor ?? bottomTheme.backgroundColor;
break;
case BottomNavigationBarType.shifting:
backgroundColor = _backgroundColor;
......@@ -873,7 +892,7 @@ class _BottomNavigationBarState extends State<BottomNavigationBar> with TickerPr
return Semantics(
explicitChildNodes: true,
child: Material(
elevation: widget.elevation,
elevation: widget.elevation ?? bottomTheme.elevation ?? 8.0,
color: backgroundColor,
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:ui' show lerpDouble;
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'bottom_navigation_bar.dart';
import 'theme.dart';
/// Defines default property values for descendant [BottomNavigationBar]
/// widgets.
///
/// Descendant widgets obtain the current [BottomNavigationBarThemeData] object
/// using `BottomNavigationBarTheme.of(context)`. Instances of
/// [BottomNavigationBarThemeData] can be customized with
/// [BottomNavigationBarThemeData.copyWith].
///
/// Typically a [BottomNavigationBarThemeData] is specified as part of the
/// overall [Theme] with [ThemeData.bottomNavigationBarTheme].
///
/// All [BottomNavigationBarThemeData] properties are `null` by default. When
/// null, the [BottomNavigationBar]'s build method provides defaults.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class BottomNavigationBarThemeData with Diagnosticable {
/// Creates a theme that can be used for [ThemeData.BottomNavigationBarTheme].
const BottomNavigationBarThemeData({
this.backgroundColor,
this.elevation,
this.selectedIconTheme,
this.unselectedIconTheme,
this.selectedItemColor,
this.unselectedItemColor,
this.selectedLabelStyle,
this.unselectedLabelStyle,
this.showSelectedLabels,
this.showUnselectedLabels,
this.type,
});
/// The color of the [BottomNavigationBar] itself.
///
/// See [BottomNavigationBar.backgroundColor].
final Color backgroundColor;
/// The z-coordinate of the [BottomNavigationBar].
///
/// See [BottomNavigationBar.elevation].
final double elevation;
/// The size, opacity, and color of the icon in the currently selected
/// [BottomNavigationBarItem.icon].
///
/// See [BottomNavigationBar.selectedIconTheme].
final IconThemeData selectedIconTheme;
/// The size, opacity, and color of the icon in the currently unselected
/// [BottomNavigationBarItem.icon]s.
///
/// See [BottomNavigationBar.unselectedIconTheme].
final IconThemeData unselectedIconTheme;
/// The color of the selected [BottomNavigationBarItem.icon] and
/// [BottomNavigationBarItem.label].
///
/// See [BottomNavigationBar.selectedItemColor].
final Color selectedItemColor;
/// The color of the unselected [BottomNavigationBarItem.icon] and
/// [BottomNavigationBarItem.label]s.
///
/// See [BottomNavigationBar.unselectedItemColor].
final Color unselectedItemColor;
/// The [TextStyle] of the [BottomNavigationBarItem] labels when they are
/// selected.
///
/// See [BottomNavigationBar.selectedLabelStyle].
final TextStyle selectedLabelStyle;
/// The [TextStyle] of the [BottomNavigationBarItem] labels when they are not
/// selected.
///
/// See [BottomNavigationBar.unselectedLabelStyle].
final TextStyle unselectedLabelStyle;
/// Whether the labels are shown for the unselected [BottomNavigationBarItem]s.
///
/// See [BottomNavigationBar.showSelectedLabels].
final bool showSelectedLabels;
/// Whether the labels are shown for the selected [BottomNavigationBarItem].
///
/// See [BottomNavigationBar.showUnselectedLabels].
final bool showUnselectedLabels;
/// Defines the layout and behavior of a [BottomNavigationBar].
///
/// See [BottomNavigationBar.type].
final BottomNavigationBarType type;
/// Creates a copy of this object but with the given fields replaced with the
/// new values.
BottomNavigationBarThemeData copyWith({
Color backgroundColor,
double elevation,
IconThemeData selectedIconTheme,
IconThemeData unselectedIconTheme,
Color selectedItemColor,
Color unselectedItemColor,
TextStyle selectedLabelStyle,
TextStyle unselectedLabelStyle,
bool showSelectedLabels,
bool showUnselectedLabels,
BottomNavigationBarType type,
}) {
return BottomNavigationBarThemeData(
backgroundColor: backgroundColor ?? this.backgroundColor,
elevation: elevation ?? this.elevation,
selectedIconTheme: selectedIconTheme ?? this.selectedIconTheme,
unselectedIconTheme: unselectedIconTheme ?? this.unselectedIconTheme,
selectedItemColor: selectedItemColor ?? this.selectedItemColor,
unselectedItemColor: unselectedItemColor ?? this.unselectedItemColor,
selectedLabelStyle: selectedLabelStyle ?? this.selectedLabelStyle,
unselectedLabelStyle: unselectedLabelStyle ?? this.unselectedLabelStyle,
showSelectedLabels: showSelectedLabels ?? this.showSelectedLabels,
showUnselectedLabels: showUnselectedLabels ?? this.showUnselectedLabels,
type: type ?? this.type,
);
}
/// Linearly interpolate between two [BottomNavigationBarThemeData].
///
/// The argument `t` must not be null.
///
/// {@macro dart.ui.shadow.lerp}
static BottomNavigationBarThemeData lerp(BottomNavigationBarThemeData a, BottomNavigationBarThemeData b, double t) {
assert(t != null);
return BottomNavigationBarThemeData(
backgroundColor: Color.lerp(a?.backgroundColor, b?.backgroundColor, t),
elevation: lerpDouble(a?.elevation, b?.elevation, t),
selectedIconTheme: IconThemeData.lerp(a?.selectedIconTheme, b?.selectedIconTheme, t),
unselectedIconTheme: IconThemeData.lerp(a?.unselectedIconTheme, b?.unselectedIconTheme, t),
selectedItemColor: Color.lerp(a?.selectedItemColor, b?.selectedItemColor, t),
unselectedItemColor: Color.lerp(a?.unselectedItemColor, b?.unselectedItemColor, t),
selectedLabelStyle: TextStyle.lerp(a?.selectedLabelStyle, b?.selectedLabelStyle, t),
unselectedLabelStyle: TextStyle.lerp(a?.unselectedLabelStyle, b?.unselectedLabelStyle, t),
showSelectedLabels: t < 0.5 ? a?.showSelectedLabels : b?.showSelectedLabels,
showUnselectedLabels: t < 0.5 ? a?.showUnselectedLabels : b?.showUnselectedLabels,
type: t < 0.5 ? a?.type : b?.type,
);
}
@override
int get hashCode {
return hashValues(
backgroundColor,
elevation,
selectedIconTheme,
unselectedIconTheme,
selectedItemColor,
unselectedItemColor,
selectedLabelStyle,
unselectedLabelStyle,
showSelectedLabels,
showUnselectedLabels,
type,
);
}
@override
bool operator ==(Object other) {
if (identical(this, other))
return true;
if (other.runtimeType != runtimeType)
return false;
return other is BottomNavigationBarThemeData
&& other.backgroundColor == backgroundColor
&& other.elevation == elevation
&& other.selectedIconTheme == selectedIconTheme
&& other.unselectedIconTheme == unselectedIconTheme
&& other.selectedItemColor == selectedItemColor
&& other.unselectedItemColor == unselectedItemColor
&& other.selectedLabelStyle == selectedLabelStyle
&& other.unselectedLabelStyle == unselectedLabelStyle
&& other.showSelectedLabels == showSelectedLabels
&& other.showUnselectedLabels == showUnselectedLabels
&& other.type == type;
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(ColorProperty('backgroundColor', backgroundColor, defaultValue: null));
properties.add(DoubleProperty('elevation', elevation, defaultValue: null));
properties.add(DiagnosticsProperty<IconThemeData>('selectedIconTheme', selectedIconTheme, defaultValue: null));
properties.add(DiagnosticsProperty<IconThemeData>('unselectedIconTheme', unselectedIconTheme, defaultValue: null));
properties.add(ColorProperty('selectedItemColor', selectedItemColor, defaultValue: null));
properties.add(ColorProperty('unselectedItemColor', unselectedItemColor, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('selectedLabelStyle', selectedLabelStyle, defaultValue: null));
properties.add(DiagnosticsProperty<TextStyle>('unselectedLabelStyle', unselectedLabelStyle, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('showSelectedLabels', showSelectedLabels, defaultValue: null));
properties.add(DiagnosticsProperty<bool>('showUnselectedLabels', showUnselectedLabels, defaultValue: null));
properties.add(DiagnosticsProperty<BottomNavigationBarType>('type', type, defaultValue: null));
}
}
/// Applies a bottom navigation bar theme to descendant [BottomNavigationBar]
/// widgets.
///
/// Descendant widgets obtain the current theme's [BottomNavigationBarTheme]
/// object using [BottomNavigationBarTheme.of]. When a widget uses
/// [BottomNavigationBarTheme.of], it is automatically rebuilt if the theme
/// later changes.
///
/// A bottom navigation theme can be specified as part of the overall Material
/// theme using [ThemeData.bottomNavigationBarTheme].
///
/// See also:
///
/// * [BottomNavigationBarThemeData], which describes the actual configuration
/// of a bottom navigation bar theme.
class BottomNavigationBarTheme extends InheritedWidget {
/// Constructs a bottom navigation bar theme that configures all descendant
/// [BottomNavigationBar] widgets.
///
/// The [data] must not be null.
const BottomNavigationBarTheme({
Key key,
@required this.data,
Widget child,
}) : assert(data != null), super(key: key, child: child);
/// The properties used for all descendant [BottomNavigationBar] widgets.
final BottomNavigationBarThemeData data;
/// Returns the configuration [data] from the closest
/// [BottomNavigationBarTheme] ancestor. If there is no ancestor, it returns
/// [ThemeData.bottomNavigationBarTheme]. Applications can assume that the
/// returned value will not be null.
///
/// Typical usage is as follows:
///
/// ```dart
/// BottomNavigationBarThemeData theme = BottomNavigationBarTheme.of(context);
/// ```
static BottomNavigationBarThemeData of(BuildContext context) {
final BottomNavigationBarTheme bottomNavTheme = context.dependOnInheritedWidgetOfExactType<BottomNavigationBarTheme>();
return bottomNavTheme?.data ?? Theme.of(context).bottomNavigationBarTheme;
}
@override
bool updateShouldNotify(BottomNavigationBarTheme oldWidget) => data != oldWidget.data;
}
......@@ -12,6 +12,7 @@ import 'package:flutter/widgets.dart';
import 'app_bar_theme.dart';
import 'banner_theme.dart';
import 'bottom_app_bar_theme.dart';
import 'bottom_navigation_bar_theme.dart';
import 'bottom_sheet_theme.dart';
import 'button_bar_theme.dart';
import 'button_theme.dart';
......@@ -266,6 +267,7 @@ class ThemeData with Diagnosticable {
MaterialBannerThemeData bannerTheme,
DividerThemeData dividerTheme,
ButtonBarThemeData buttonBarTheme,
BottomNavigationBarThemeData bottomNavigationBarTheme,
}) {
brightness ??= Brightness.light;
final bool isDark = brightness == Brightness.dark;
......@@ -374,6 +376,7 @@ class ThemeData with Diagnosticable {
bannerTheme ??= const MaterialBannerThemeData();
dividerTheme ??= const DividerThemeData();
buttonBarTheme ??= const ButtonBarThemeData();
bottomNavigationBarTheme ??= const BottomNavigationBarThemeData();
return ThemeData.raw(
brightness: brightness,
......@@ -440,6 +443,7 @@ class ThemeData with Diagnosticable {
bannerTheme: bannerTheme,
dividerTheme: dividerTheme,
buttonBarTheme: buttonBarTheme,
bottomNavigationBarTheme: bottomNavigationBarTheme,
);
}
......@@ -518,6 +522,7 @@ class ThemeData with Diagnosticable {
@required this.bannerTheme,
@required this.dividerTheme,
@required this.buttonBarTheme,
@required this.bottomNavigationBarTheme,
}) : assert(brightness != null),
assert(visualDensity != null),
assert(primaryColor != null),
......@@ -578,7 +583,8 @@ class ThemeData with Diagnosticable {
assert(popupMenuTheme != null),
assert(bannerTheme != null),
assert(dividerTheme != null),
assert(buttonBarTheme != null);
assert(buttonBarTheme != null),
assert(bottomNavigationBarTheme != null);
/// Create a [ThemeData] based on the colors in the given [colorScheme] and
/// text styles of the optional [textTheme].
......@@ -1025,6 +1031,10 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the appearance and layout of [ButtonBar] widgets.
final ButtonBarThemeData buttonBarTheme;
/// A theme for customizing the appearance and layout of [BottomNavigationBar]
/// widgets.
final BottomNavigationBarThemeData bottomNavigationBarTheme;
/// Creates a copy of this theme but with the given fields replaced with the new values.
ThemeData copyWith({
Brightness brightness,
......@@ -1091,6 +1101,7 @@ class ThemeData with Diagnosticable {
MaterialBannerThemeData bannerTheme,
DividerThemeData dividerTheme,
ButtonBarThemeData buttonBarTheme,
BottomNavigationBarThemeData bottomNavigationBarTheme,
}) {
cupertinoOverrideTheme = cupertinoOverrideTheme?.noDefault();
return ThemeData.raw(
......@@ -1158,6 +1169,7 @@ class ThemeData with Diagnosticable {
bannerTheme: bannerTheme ?? this.bannerTheme,
dividerTheme: dividerTheme ?? this.dividerTheme,
buttonBarTheme: buttonBarTheme ?? this.buttonBarTheme,
bottomNavigationBarTheme: bottomNavigationBarTheme ?? this.bottomNavigationBarTheme,
);
}
......@@ -1303,6 +1315,7 @@ class ThemeData with Diagnosticable {
bannerTheme: MaterialBannerThemeData.lerp(a.bannerTheme, b.bannerTheme, t),
dividerTheme: DividerThemeData.lerp(a.dividerTheme, b.dividerTheme, t),
buttonBarTheme: ButtonBarThemeData.lerp(a.buttonBarTheme, b.buttonBarTheme, t),
bottomNavigationBarTheme: BottomNavigationBarThemeData.lerp(a.bottomNavigationBarTheme, b.bottomNavigationBarTheme, t),
);
}
......@@ -1375,7 +1388,8 @@ class ThemeData with Diagnosticable {
&& other.popupMenuTheme == popupMenuTheme
&& other.bannerTheme == bannerTheme
&& other.dividerTheme == dividerTheme
&& other.buttonBarTheme == buttonBarTheme;
&& other.buttonBarTheme == buttonBarTheme
&& other.bottomNavigationBarTheme == bottomNavigationBarTheme;
}
@override
......@@ -1448,6 +1462,7 @@ class ThemeData with Diagnosticable {
bannerTheme,
dividerTheme,
buttonBarTheme,
bottomNavigationBarTheme,
];
return hashList(values);
}
......@@ -1516,6 +1531,7 @@ class ThemeData with Diagnosticable {
properties.add(DiagnosticsProperty<MaterialBannerThemeData>('bannerTheme', bannerTheme, defaultValue: defaultData.bannerTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<DividerThemeData>('dividerTheme', dividerTheme, defaultValue: defaultData.dividerTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<ButtonBarThemeData>('buttonBarTheme', buttonBarTheme, defaultValue: defaultData.buttonBarTheme, level: DiagnosticLevel.debug));
properties.add(DiagnosticsProperty<BottomNavigationBarThemeData>('bottomNavigationBarTheme', bottomNavigationBarTheme, defaultValue: defaultData.bottomNavigationBarTheme, level: DiagnosticLevel.debug));
}
}
......
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
void main() {
test('BottomNavigationBarThemeData copyWith, ==, hashCode basics', () {
expect(const BottomNavigationBarThemeData(), const BottomNavigationBarThemeData().copyWith());
expect(const BottomNavigationBarThemeData().hashCode, const BottomNavigationBarThemeData().copyWith().hashCode);
});
test('BottomNavigationBarThemeData defaults', () {
const BottomNavigationBarThemeData themeData = BottomNavigationBarThemeData();
expect(themeData.backgroundColor, null);
expect(themeData.elevation, null);
expect(themeData.selectedIconTheme, null);
expect(themeData.unselectedIconTheme, null);
expect(themeData.selectedItemColor, null);
expect(themeData.unselectedItemColor, null);
expect(themeData.selectedLabelStyle, null);
expect(themeData.unselectedLabelStyle, null);
expect(themeData.showSelectedLabels, null);
expect(themeData.showUnselectedLabels, null);
expect(themeData.type, null);
const BottomNavigationBarTheme theme = BottomNavigationBarTheme(data: BottomNavigationBarThemeData());
expect(theme.data.backgroundColor, null);
expect(theme.data.elevation, null);
expect(theme.data.selectedIconTheme, null);
expect(theme.data.unselectedIconTheme, null);
expect(theme.data.selectedItemColor, null);
expect(theme.data.unselectedItemColor, null);
expect(theme.data.selectedLabelStyle, null);
expect(theme.data.unselectedLabelStyle, null);
expect(theme.data.showSelectedLabels, null);
expect(theme.data.showUnselectedLabels, null);
expect(theme.data.type, null);
});
testWidgets('Default BottomNavigationBarThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const BottomNavigationBarThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('BottomNavigationBarThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const BottomNavigationBarThemeData(
backgroundColor: Color(0xfffffff0),
elevation: 10.0,
selectedIconTheme: IconThemeData(size: 1.0),
unselectedIconTheme: IconThemeData(size: 2.0),
selectedItemColor: Color(0xfffffff1),
unselectedItemColor: Color(0xfffffff2),
selectedLabelStyle: TextStyle(fontSize: 3.0),
unselectedLabelStyle: TextStyle(fontSize: 4.0),
showSelectedLabels: true,
showUnselectedLabels: true,
type: BottomNavigationBarType.fixed,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description[0], 'backgroundColor: Color(0xfffffff0)');
expect(description[1], 'elevation: 10.0');
// Ignore instance address for IconThemeData.
expect(description[2].contains('selectedIconTheme: IconThemeData'), isTrue);
expect(description[2].contains('(size: 1.0)'), isTrue);
expect(description[3].contains('unselectedIconTheme: IconThemeData'), isTrue);
expect(description[3].contains('(size: 2.0)'), isTrue);
expect(description[4], 'selectedItemColor: Color(0xfffffff1)');
expect(description[5], 'unselectedItemColor: Color(0xfffffff2)');
expect(description[6], 'selectedLabelStyle: TextStyle(inherit: true, size: 3.0)');
expect(description[7], 'unselectedLabelStyle: TextStyle(inherit: true, size: 4.0)');
expect(description[8], 'showSelectedLabels: true');
expect(description[9], 'showUnselectedLabels: true');
expect(description[10], 'type: BottomNavigationBarType.fixed');
});
testWidgets('BottomNavigationBar is themable', (WidgetTester tester) async {
const Color backgroundColor = Color(0xFF000001);
const Color selectedItemColor = Color(0xFF000002);
const Color unselectedItemColor = Color(0xFF000003);
const IconThemeData selectedIconTheme = IconThemeData(size: 10);
const IconThemeData unselectedIconTheme = IconThemeData(size: 11);
const TextStyle selectedTextStyle = TextStyle(fontSize: 22);
const TextStyle unselectedTextStyle = TextStyle(fontSize: 21);
const double elevation = 9.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: backgroundColor,
selectedItemColor: selectedItemColor,
unselectedItemColor: unselectedItemColor,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
elevation: elevation,
showUnselectedLabels: true,
showSelectedLabels: true,
type: BottomNavigationBarType.fixed,
selectedLabelStyle: selectedTextStyle,
unselectedLabelStyle: unselectedTextStyle,
),
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
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 selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedFontStyle.fontSize, selectedFontStyle.fontSize);
// Unselected label has a font size of 22 but is scaled down to be font size 21.
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(selectedIcon.color, equals(selectedItemColor));
expect(selectedIcon.fontSize, equals(selectedIconTheme.size));
expect(unselectedIcon.color, equals(unselectedItemColor));
expect(unselectedIcon.fontSize, equals(unselectedIconTheme.size));
// There should not be any [Opacity] or [FadeTransition] widgets
// since showUnselectedLabels and showSelectedLabels are true.
final Finder findOpacity = find.descendant(
of: find.byType(BottomNavigationBar),
matching: find.byType(Opacity),
);
final Finder findFadeTransition = find.descendant(
of: find.byType(BottomNavigationBar),
matching: find.byType(FadeTransition),
);
expect(findOpacity, findsNothing);
expect(findFadeTransition, findsNothing);
expect(_material(tester).elevation, equals(elevation));
expect(_material(tester).color, equals(backgroundColor));
});
testWidgets('BottomNavigationBar properties are taken over the theme values', (WidgetTester tester) async {
const Color themeBackgroundColor = Color(0xFF000001);
const Color themeSelectedItemColor = Color(0xFF000002);
const Color themeUnselectedItemColor = Color(0xFF000003);
const IconThemeData themeSelectedIconTheme = IconThemeData(size: 10);
const IconThemeData themeUnselectedIconTheme = IconThemeData(size: 11);
const TextStyle themeSelectedTextStyle = TextStyle(fontSize: 22);
const TextStyle themeUnselectedTextStyle = TextStyle(fontSize: 21);
const double themeElevation = 9.0;
const Color backgroundColor = Color(0xFF000004);
const Color selectedItemColor = Color(0xFF000005);
const Color unselectedItemColor = Color(0xFF000006);
const IconThemeData selectedIconTheme = IconThemeData(size: 15);
const IconThemeData unselectedIconTheme = IconThemeData(size: 16);
const TextStyle selectedTextStyle = TextStyle(fontSize: 25);
const TextStyle unselectedTextStyle = TextStyle(fontSize: 26);
const double elevation = 7.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: themeBackgroundColor,
selectedItemColor: themeSelectedItemColor,
unselectedItemColor: themeUnselectedItemColor,
selectedIconTheme: themeSelectedIconTheme,
unselectedIconTheme: themeUnselectedIconTheme,
elevation: themeElevation,
showUnselectedLabels: false,
showSelectedLabels: false,
type: BottomNavigationBarType.shifting,
selectedLabelStyle: themeSelectedTextStyle,
unselectedLabelStyle: themeUnselectedTextStyle,
),
),
home: Scaffold(
bottomNavigationBar: BottomNavigationBar(
backgroundColor: backgroundColor,
selectedItemColor: selectedItemColor,
unselectedItemColor: unselectedItemColor,
selectedIconTheme: selectedIconTheme,
unselectedIconTheme: unselectedIconTheme,
elevation: elevation,
showUnselectedLabels: true,
showSelectedLabels: true,
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 selectedIcon = _iconStyle(tester, Icons.ac_unit);
final TextStyle unselectedIcon = _iconStyle(tester, Icons.access_alarm);
expect(selectedFontStyle.fontSize, selectedFontStyle.fontSize);
// Unselected label has a font size of 22 but is scaled down to be font size 21.
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(selectedIcon.color, equals(selectedItemColor));
expect(selectedIcon.fontSize, equals(selectedIconTheme.size));
expect(unselectedIcon.color, equals(unselectedItemColor));
expect(unselectedIcon.fontSize, equals(unselectedIconTheme.size));
// There should not be any [Opacity] or [FadeTransition] widgets
// since showUnselectedLabels and showSelectedLabels are true.
final Finder findOpacity = find.descendant(
of: find.byType(BottomNavigationBar),
matching: find.byType(Opacity),
);
final Finder findFadeTransition = find.descendant(
of: find.byType(BottomNavigationBar),
matching: find.byType(FadeTransition),
);
expect(findOpacity, findsNothing);
expect(findFadeTransition, findsNothing);
expect(_material(tester).elevation, equals(elevation));
expect(_material(tester).color, equals(backgroundColor));
});
}
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;
}
Material _material(WidgetTester tester) {
return tester.firstWidget<Material>(
find.descendant(of: find.byType(BottomNavigationBar), matching: find.byType(Material)),
);
}
......@@ -280,6 +280,7 @@ void main() {
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.black),
dividerTheme: const DividerThemeData(color: Colors.black),
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.start),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.fixed),
);
final SliderThemeData otherSliderTheme = SliderThemeData.fromPrimaryColors(
......@@ -360,6 +361,7 @@ void main() {
bannerTheme: const MaterialBannerThemeData(backgroundColor: Colors.white),
dividerTheme: const DividerThemeData(color: Colors.white),
buttonBarTheme: const ButtonBarThemeData(alignment: MainAxisAlignment.end),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(type: BottomNavigationBarType.shifting),
);
final ThemeData themeDataCopy = theme.copyWith(
......@@ -426,6 +428,7 @@ void main() {
bannerTheme: otherTheme.bannerTheme,
dividerTheme: otherTheme.dividerTheme,
buttonBarTheme: otherTheme.buttonBarTheme,
bottomNavigationBarTheme: otherTheme.bottomNavigationBarTheme,
);
expect(themeDataCopy.brightness, equals(otherTheme.brightness));
......@@ -493,6 +496,7 @@ void main() {
expect(themeDataCopy.bannerTheme, equals(otherTheme.bannerTheme));
expect(themeDataCopy.dividerTheme, equals(otherTheme.dividerTheme));
expect(themeDataCopy.buttonBarTheme, equals(otherTheme.buttonBarTheme));
expect(themeDataCopy.bottomNavigationBarTheme, equals(otherTheme.bottomNavigationBarTheme));
});
testWidgets('ThemeData.toString has less than 200 characters output', (WidgetTester tester) async {
......
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