Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
f3049c77
Unverified
Commit
f3049c77
authored
Sep 26, 2021
by
MH Johnson
Committed by
GitHub
Sep 26, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add navigation bar component (#83047)
parent
61bce1d8
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
2064 additions
and
0 deletions
+2064
-0
material.dart
packages/flutter/lib/material.dart
+2
-0
navigation_bar.dart
packages/flutter/lib/src/material/navigation_bar.dart
+1164
-0
navigation_bar_theme.dart
packages/flutter/lib/src/material/navigation_bar_theme.dart
+228
-0
theme_data.dart
packages/flutter/lib/src/material/theme_data.dart
+16
-0
navigation_bar_test.dart
packages/flutter/test/material/navigation_bar_test.dart
+396
-0
navigation_bar_theme_test.dart
...ages/flutter/test/material/navigation_bar_theme_test.dart
+254
-0
theme_data_test.dart
packages/flutter/test/material/theme_data_test.dart
+4
-0
No files found.
packages/flutter/lib/material.dart
View file @
f3049c77
...
@@ -99,6 +99,8 @@ export 'src/material/material_localizations.dart';
...
@@ -99,6 +99,8 @@ export 'src/material/material_localizations.dart';
export
'src/material/material_state.dart'
;
export
'src/material/material_state.dart'
;
export
'src/material/material_state_mixin.dart'
;
export
'src/material/material_state_mixin.dart'
;
export
'src/material/mergeable_material.dart'
;
export
'src/material/mergeable_material.dart'
;
export
'src/material/navigation_bar.dart'
;
export
'src/material/navigation_bar_theme.dart'
;
export
'src/material/navigation_rail.dart'
;
export
'src/material/navigation_rail.dart'
;
export
'src/material/navigation_rail_theme.dart'
;
export
'src/material/navigation_rail_theme.dart'
;
export
'src/material/no_splash.dart'
;
export
'src/material/no_splash.dart'
;
...
...
packages/flutter/lib/src/material/navigation_bar.dart
0 → 100644
View file @
f3049c77
// 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/widgets.dart'
;
import
'color_scheme.dart'
;
import
'colors.dart'
;
import
'elevation_overlay.dart'
;
import
'ink_well.dart'
;
import
'material.dart'
;
import
'material_localizations.dart'
;
import
'material_state.dart'
;
import
'navigation_bar_theme.dart'
;
import
'theme.dart'
;
import
'tooltip.dart'
;
/// Material 3 Navigation Bar component.
///
/// Navigation bars offer a persistent and convenient way to switch between
/// primary destinations in an app.
///
/// This widget does not adjust its size with the [ThemeData.visualDensity].
///
/// The [MediaQueryData.textScaleFactor] does not adjust the size of this widget but
/// rather the size of the [Tooltip]s displayed on long presses of the
/// destinations.
///
/// The style for the icons and text are not affected by parent
/// [DefaultTextStyle]s or [IconTheme]s but rather controlled by parameters or
/// the [NavigationBarThemeData].
///
/// This widget holds a collection of destinations (usually
/// [NavigationDestination]s).
///
/// Usage:
/// ```dart
/// Scaffold(
/// bottomNavigationBar: NavigationBar(
/// onDestinationSelected: (int index) {
/// setState(() { _currentPageIndex = index; }),
/// },
/// selectedIndex: _currentPageIndex,
/// destinations: [
/// NavigationDestination(
/// icon: Icon(Icons.explore),
/// label: 'Explore',
/// ),
/// NavigationDestination(
/// icon: Icon(Icons.commute),
/// label: 'Commute',
/// ),
/// NavigationDestination(
/// selectedIcon: Icon(Icons.bookmark),
/// icon: Icon(Icons.bookmark_border),
/// label: 'Saved',
/// ),
/// ],
/// ),
/// ),
/// ```
class
NavigationBar
extends
StatelessWidget
{
/// Creates a Material 3 Navigation Bar component.
///
/// The value of [destinations] must be a list of two or more
/// [NavigationDestination] values.
const
NavigationBar
({
Key
?
key
,
this
.
animationDuration
,
this
.
selectedIndex
=
0
,
required
this
.
destinations
,
this
.
onDestinationSelected
,
this
.
backgroundColor
,
this
.
height
,
this
.
labelBehavior
,
})
:
assert
(
destinations
!=
null
&&
destinations
.
length
>=
2
),
assert
(
0
<=
selectedIndex
&&
selectedIndex
<
destinations
.
length
),
super
(
key:
key
);
/// Determines the transition time for each destination as it goes between
/// selected and unselected.
final
Duration
?
animationDuration
;
/// Determines which one of the [destinations] is currently selected.
///
/// When this is updated, the destination (from [destinations]) at
/// [selectedIndex] goes from unselected to selected.
final
int
selectedIndex
;
/// The list of destinations (usually [NavigationDestination]s) in this
/// [NavigationBar].
///
/// When [selectedIndex] is updated, the destination from this list at
/// [selectedIndex] will animate from 0 (unselected) to 1.0 (selected). When
/// the animation is increasing or completed, the destination is considered
/// selected, when the animation is decreasing or dismissed, the destination
/// is considered unselected.
final
List
<
Widget
>
destinations
;
/// Called when one of the [destinations] is selected.
///
/// This callback usually updates the int passed to [selectedIndex].
///
/// Upon updating [selectedIndex], the [NavigationBar] will be rebuilt.
final
ValueChanged
<
int
>?
onDestinationSelected
;
/// The color of the [NavigationBar] itself.
///
/// If null, [NavigationBarThemeData.backgroundColor] is used. If that
/// is also null, the default blends [ColorScheme.surface] and
/// [ColorScheme.onSurface] using an [ElevationOverlay].
final
Color
?
backgroundColor
;
/// The height of the [NavigationBar] itself.
///
/// If this is used in [Scaffold.bottomNavigationBar] and the scaffold is
/// full-screen, the safe area padding is also added to the height
/// automatically.
///
/// The height does not adjust with [ThemeData.visualDensity] or
/// [MediaQueryData.textScaleFactor] as this component loses usability at
/// larger and smaller sizes due to the truncating of labels or smaller tap
/// targets.
///
/// If null, [NavigationBarThemeData.height] is used. If that
/// is also null, the default is 80.
final
double
?
height
;
/// Defines how the [destinations]' labels will be laid out and when they'll
/// be displayed.
///
/// Can be used to show all labels, show only the selected label, or hide all
/// labels.
///
/// If null, [NavigationBarThemeData.labelBehavior] is used. If that
/// is also null, the default is
/// [NavigationDestinationLabelBehavior.alwaysShow].
final
NavigationDestinationLabelBehavior
?
labelBehavior
;
VoidCallback
_handleTap
(
int
index
)
{
return
onDestinationSelected
!=
null
?
()
=>
onDestinationSelected
!(
index
)
:
()
{};
}
@override
Widget
build
(
BuildContext
context
)
{
final
ColorScheme
colorScheme
=
Theme
.
of
(
context
).
colorScheme
;
final
NavigationBarThemeData
navigationBarTheme
=
NavigationBarTheme
.
of
(
context
);
final
double
effectiveHeight
=
height
??
navigationBarTheme
.
height
??
80
;
final
NavigationDestinationLabelBehavior
effectiveLabelBehavior
=
labelBehavior
??
navigationBarTheme
.
labelBehavior
??
NavigationDestinationLabelBehavior
.
alwaysShow
;
final
double
additionalBottomPadding
=
MediaQuery
.
of
(
context
).
padding
.
bottom
;
return
Material
(
// With Material 3, the NavigationBar uses an overlay blend for the
// default color regardless of light/dark mode.
color:
backgroundColor
??
navigationBarTheme
.
backgroundColor
??
ElevationOverlay
.
colorWithOverlay
(
colorScheme
.
surface
,
colorScheme
.
onSurface
,
3.0
),
child:
Padding
(
padding:
EdgeInsets
.
only
(
bottom:
additionalBottomPadding
),
child:
MediaQuery
.
removePadding
(
context:
context
,
removeBottom:
true
,
child:
SizedBox
(
height:
effectiveHeight
,
child:
Row
(
children:
<
Widget
>[
for
(
int
i
=
0
;
i
<
destinations
.
length
;
i
++)
Expanded
(
child:
_SelectableAnimatedBuilder
(
duration:
animationDuration
??
const
Duration
(
milliseconds:
500
),
isSelected:
i
==
selectedIndex
,
builder:
(
BuildContext
context
,
Animation
<
double
>
animation
)
{
return
_NavigationDestinationInfo
(
index:
i
,
totalNumberOfDestinations:
destinations
.
length
,
selectedAnimation:
animation
,
labelBehavior:
effectiveLabelBehavior
,
onTap:
_handleTap
(
i
),
child:
destinations
[
i
],
);
},
),
),
],
),
),
),
),
);
}
}
/// Specifies when each [NavigationDestination]'s label should appear.
///
/// This is used to determine the behavior of [NavigationBar]'s destinations.
enum
NavigationDestinationLabelBehavior
{
/// Always shows all of the labels under each navigation bar destination,
/// selected and unselected.
alwaysShow
,
/// Never shows any of the labels under the navigation bar destinations,
/// regardless of selected vs unselected.
alwaysHide
,
/// Only shows the labels of the selected navigation bar destination.
///
/// When a destination is unselected, the label will be faded out, and the
/// icon will be centered.
///
/// When a destination is selected, the label will fade in and the label and
/// icon will slide up so that they are both centered.
onlyShowSelected
,
}
/// Destination Widget for displaying Icons + labels in the Material 3
/// Navigation Bars through [NavigationBar.destinations].
///
/// The destination this widget creates will look something like this:
/// =======
/// |
/// | ☆ <-- [icon] (or [selectedIcon])
/// | text <-- [label]
/// |
/// =======
class
NavigationDestination
extends
StatelessWidget
{
/// Creates a navigation bar destination with an icon and a label, to be used
/// in the [NavigationBar.destinations].
const
NavigationDestination
({
Key
?
key
,
required
this
.
icon
,
this
.
selectedIcon
,
required
this
.
label
,
this
.
tooltip
,
})
:
super
(
key:
key
);
/// The [Widget] (usually an [Icon]) that's displayed for this
/// [NavigationDestination].
///
/// The icon will use [NavigationBarThemeData.iconTheme]. If this is
/// null, the default [IconThemeData] would use a size of 24.0 and
/// [ColorScheme.onSurface].
final
Widget
icon
;
/// The optional [Widget] (usually an [Icon]) that's displayed when this
/// [NavigationDestination] is selected.
///
/// If [selectedIcon] is non-null, the destination will fade from
/// [icon] to [selectedIcon] when this destination goes from unselected to
/// selected.
///
/// The icon will use [NavigationBarThemeData.iconTheme] with
/// [MaterialState.selected]. If this is null, the default [IconThemeData]
/// would use a size of 24.0 and [ColorScheme.onSurface].
final
Widget
?
selectedIcon
;
/// The text label that appears below the icon of this
/// [NavigationDestination].
///
/// The accompanying [Text] widget will use
/// [NavigationBarThemeData.labelTextStyle]. If this are null, the default
/// text style would use [TextTheme.overline] with [ColorScheme.onSurface].
final
String
label
;
/// The text to display in the tooltip for this [NavigationDestination], when
/// the user long presses the destination.
///
/// If [tooltip] is an empty string, no tooltip will be used.
///
/// Defaults to null, in which case the [label] text will be used.
final
String
?
tooltip
;
@override
Widget
build
(
BuildContext
context
)
{
final
ThemeData
theme
=
Theme
.
of
(
context
);
final
ColorScheme
colorScheme
=
theme
.
colorScheme
;
final
NavigationBarThemeData
navigationBarTheme
=
NavigationBarTheme
.
of
(
context
);
final
Animation
<
double
>
animation
=
_NavigationDestinationInfo
.
of
(
context
).
selectedAnimation
;
return
_NavigationDestinationBuilder
(
label:
label
,
tooltip:
tooltip
,
buildIcon:
(
BuildContext
context
)
{
final
IconThemeData
defaultIconTheme
=
IconThemeData
(
size:
24
,
color:
colorScheme
.
onSurface
,
);
final
Widget
selectedIconWidget
=
IconTheme
.
merge
(
data:
navigationBarTheme
.
iconTheme
?.
resolve
(<
MaterialState
>{
MaterialState
.
selected
})
??
defaultIconTheme
,
child:
selectedIcon
??
icon
,
);
final
Widget
unselectedIconWidget
=
IconTheme
.
merge
(
data:
navigationBarTheme
.
iconTheme
?.
resolve
(<
MaterialState
>{})
??
defaultIconTheme
,
child:
icon
,
);
return
Stack
(
alignment:
Alignment
.
center
,
children:
<
Widget
>[
_NavigationIndicator
(
animation:
animation
,
color:
navigationBarTheme
.
indicatorColor
,
),
_StatusTransitionWidgetBuilder
(
animation:
animation
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
animation
.
isForwardOrCompleted
?
selectedIconWidget
:
unselectedIconWidget
;
},
),
],
);
},
buildLabel:
(
BuildContext
context
)
{
final
TextStyle
?
defaultTextStyle
=
theme
.
textTheme
.
overline
?.
copyWith
(
color:
colorScheme
.
onSurface
,
);
final
TextStyle
?
effectiveSelectedLabelTextStyle
=
navigationBarTheme
.
labelTextStyle
?.
resolve
(<
MaterialState
>{
MaterialState
.
selected
})
??
defaultTextStyle
;
final
TextStyle
?
effectiveUnselectedLabelTextStyle
=
navigationBarTheme
.
labelTextStyle
?.
resolve
(<
MaterialState
>{})
??
defaultTextStyle
;
return
Padding
(
padding:
const
EdgeInsets
.
only
(
top:
4
),
child:
_ClampTextScaleFactor
(
// Don't scale labels of destinations, instead, tooltip text will
// upscale.
upperLimit:
1
,
child:
Text
(
label
,
style:
animation
.
isForwardOrCompleted
?
effectiveSelectedLabelTextStyle
:
effectiveUnselectedLabelTextStyle
,
),
),
);
},
);
}
}
/// Widget that handles the semantics and layout of a navigation bar
/// destination.
///
/// Prefer [NavigationDestination] over this widget, as it is a simpler
/// (although less customizable) way to get navigation bar destinations.
///
/// The icon and label of this destination are built with [buildIcon] and
/// [buildLabel]. They should build the unselected and selected icon and label
/// according to [_NavigationDestinationInfo.selectedAnimation], where an
/// animation value of 0 is unselected and 1 is selected.
///
/// See [NavigationDestination] for an example.
class
_NavigationDestinationBuilder
extends
StatelessWidget
{
/// Builds a destination (icon + label) to use in a Material 3 [NavigationBar].
const
_NavigationDestinationBuilder
({
Key
?
key
,
required
this
.
buildIcon
,
required
this
.
buildLabel
,
required
this
.
label
,
this
.
tooltip
,
})
:
super
(
key:
key
);
/// Builds the icon for an destination in a [NavigationBar].
///
/// To animate between unselected and selected, build the icon based on
/// [_NavigationDestinationInfo.selectedAnimation]. When the animation is 0,
/// the destination is unselected, when the animation is 1, the destination is
/// selected.
///
/// The destination is considered selected as soon as the animation is
/// increasing or completed, and it is considered unselected as soon as the
/// animation is decreasing or dismissed.
final
WidgetBuilder
buildIcon
;
/// Builds the label for an destination in a [NavigationBar].
///
/// To animate between unselected and selected, build the icon based on
/// [_NavigationDestinationInfo.selectedAnimation]. When the animation is
/// 0, the destination is unselected, when the animation is 1, the destination
/// is selected.
///
/// The destination is considered selected as soon as the animation is
/// increasing or completed, and it is considered unselected as soon as the
/// animation is decreasing or dismissed.
final
WidgetBuilder
buildLabel
;
/// The text value of what is in the label widget, this is required for
/// semantics so that screen readers and tooltips can read the proper label.
final
String
label
;
/// The text to display in the tooltip for this [NavigationDestination], when
/// the user long presses the destination.
///
/// If [tooltip] is an empty string, no tooltip will be used.
///
/// Defaults to null, in which case the [label] text will be used.
final
String
?
tooltip
;
@override
Widget
build
(
BuildContext
context
)
{
final
_NavigationDestinationInfo
info
=
_NavigationDestinationInfo
.
of
(
context
);
return
_NavigationBarDestinationSemantics
(
child:
_NavigationBarDestinationTooltip
(
message:
tooltip
??
label
,
child:
InkWell
(
highlightColor:
Colors
.
transparent
,
onTap:
info
.
onTap
,
child:
Row
(
children:
<
Widget
>[
Expanded
(
child:
_NavigationBarDestinationLayout
(
icon:
buildIcon
(
context
),
label:
buildLabel
(
context
),
),
),
],
),
),
),
);
}
}
/// Inherited widget for passing data from the [NavigationBar] to the
/// [NavigationBar.destinations] children widgets.
///
/// Useful for building navigation destinations using:
/// `_NavigationDestinationInfo.of(context)`.
class
_NavigationDestinationInfo
extends
InheritedWidget
{
/// Adds the information needed to build a navigation destination to the
/// [child] and descendants.
const
_NavigationDestinationInfo
({
Key
?
key
,
required
this
.
index
,
required
this
.
totalNumberOfDestinations
,
required
this
.
selectedAnimation
,
required
this
.
labelBehavior
,
required
this
.
onTap
,
required
Widget
child
,
})
:
super
(
key:
key
,
child:
child
);
/// Which destination index is this in the navigation bar.
///
/// For example:
/// ```dart
/// NavigationBar(
/// destinations: [
/// NavigationDestination(), // This is destination index 0.
/// NavigationDestination(), // This is destination index 1.
/// NavigationDestination(), // This is destination index 2.
/// ]
/// )
/// ```
///
/// This is required for semantics, so that each destination can have a label
/// "Tab 1 of 3", for example.
final
int
index
;
/// How many total destinations are are in this navigation bar.
///
/// This is required for semantics, so that each destination can have a label
/// "Tab 1 of 4", for example.
final
int
totalNumberOfDestinations
;
/// Indicates whether or not this destination is selected, from 0 (unselected)
/// to 1 (selected).
final
Animation
<
double
>
selectedAnimation
;
/// Determines the behavior for how the labels will layout.
///
/// Can be used to show all labels (the default), show only the selected
/// label, or hide all labels.
final
NavigationDestinationLabelBehavior
labelBehavior
;
/// The callback that should be called when this destination is tapped.
///
/// This is computed by calling [NavigationBar.onDestinationSelected]
/// with [index] passed in.
final
VoidCallback
onTap
;
/// Returns a non null [_NavigationDestinationInfo].
///
/// This will return an error if called with no [_NavigationDestinationInfo]
/// ancestor.
///
/// Used by widgets that are implementing a navigation destination info to
/// get information like the selected animation and destination number.
static
_NavigationDestinationInfo
of
(
BuildContext
context
)
{
final
_NavigationDestinationInfo
?
result
=
context
.
dependOnInheritedWidgetOfExactType
<
_NavigationDestinationInfo
>();
assert
(
result
!=
null
,
'Navigation destinations need a _NavigationDestinationInfo parent, '
'which is usually provided by NavigationBar.'
,
);
return
result
!;
}
@override
bool
updateShouldNotify
(
_NavigationDestinationInfo
oldWidget
)
{
return
index
!=
oldWidget
.
index
||
totalNumberOfDestinations
!=
oldWidget
.
totalNumberOfDestinations
||
selectedAnimation
!=
oldWidget
.
selectedAnimation
||
labelBehavior
!=
oldWidget
.
labelBehavior
||
onTap
!=
oldWidget
.
onTap
;
}
}
/// Selection Indicator for the Material 3 Navigation Bar component.
///
/// When [animation] is 0, the indicator is not present. As [animation] grows
/// from 0 to 1, the indicator scales in on the x axis.
///
/// Useful in a [Stack] widget behind the icons in the Material 3 Navigation Bar
/// to illuminate the selected destination.
class
_NavigationIndicator
extends
StatelessWidget
{
/// Builds an indicator, usually used in a stack behind the icon of a
/// navigation bar destination.
const
_NavigationIndicator
({
Key
?
key
,
required
this
.
animation
,
this
.
color
,
})
:
super
(
key:
key
);
/// Determines the scale of the indicator.
///
/// When [animation] is 0, the indicator is not present. The indicator scales
/// in as [animation] grows from 0 to 1.
final
Animation
<
double
>
animation
;
/// The fill color of this indicator.
///
/// If null, defaults to [ColorScheme.secondary].
final
Color
?
color
;
@override
Widget
build
(
BuildContext
context
)
{
final
ColorScheme
colorScheme
=
Theme
.
of
(
context
).
colorScheme
;
return
AnimatedBuilder
(
animation:
animation
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
// The scale should be 0 when the animation is unselected, as soon as
// the animation starts, the scale jumps to 40%, and then animates to
// 100% along a curve.
final
double
scale
=
animation
.
isDismissed
?
0.0
:
Tween
<
double
>(
begin:
.
4
,
end:
1.0
).
transform
(
CurveTween
(
curve:
Curves
.
easeInOutCubicEmphasized
).
transform
(
animation
.
value
));
return
Transform
(
alignment:
Alignment
.
center
,
// Scale in the X direction only.
transform:
Matrix4
.
diagonal3Values
(
scale
,
1.0
,
1.0
,
),
child:
child
,
);
},
// Fade should be a 100ms animation whenever the parent animation changes
// direction.
child:
_StatusTransitionWidgetBuilder
(
animation:
animation
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
_SelectableAnimatedBuilder
(
isSelected:
animation
.
isForwardOrCompleted
,
duration:
const
Duration
(
milliseconds:
100
),
alwaysDoFullAnimation:
true
,
builder:
(
BuildContext
context
,
Animation
<
double
>
fadeAnimation
)
{
return
FadeTransition
(
opacity:
fadeAnimation
,
child:
Container
(
width:
64.0
,
height:
32.0
,
decoration:
BoxDecoration
(
borderRadius:
BorderRadius
.
circular
(
16.0
),
color:
color
??
colorScheme
.
secondary
.
withOpacity
(.
24
),
),
),
);
},
);
},
),
);
}
}
/// Widget that handles the layout of the icon + label in a navigation bar
/// destination, based on [_NavigationDestinationInfo.labelBehavior] and
/// [_NavigationDestinationInfo.selectedAnimation].
///
/// Depending on the [_NavigationDestinationInfo.labelBehavior], the labels
/// will shift and fade accordingly.
class
_NavigationBarDestinationLayout
extends
StatelessWidget
{
/// Builds a widget to layout an icon + label for a destination in a Material
/// 3 [NavigationBar].
const
_NavigationBarDestinationLayout
({
Key
?
key
,
required
this
.
icon
,
required
this
.
label
,
})
:
super
(
key:
key
);
/// The icon widget that sits on top of the label.
///
/// See [NavigationDestination.icon].
final
Widget
icon
;
/// The label widget that sits below the icon.
///
/// This widget will sometimes be faded out, depending on
/// [_NavigationDestinationInfo.selectedAnimation].
///
/// See [NavigationDestination.label].
final
Widget
label
;
static
final
Key
_iconKey
=
UniqueKey
();
static
final
Key
_labelKey
=
UniqueKey
();
@override
Widget
build
(
BuildContext
context
)
{
return
_DestinationLayoutAnimationBuilder
(
builder:
(
BuildContext
context
,
Animation
<
double
>
animation
)
{
return
CustomMultiChildLayout
(
delegate:
_NavigationDestinationLayoutDelegate
(
animation:
animation
,
),
children:
<
Widget
>[
LayoutId
(
id:
_NavigationDestinationLayoutDelegate
.
iconId
,
child:
RepaintBoundary
(
key:
_iconKey
,
child:
icon
,
),
),
LayoutId
(
id:
_NavigationDestinationLayoutDelegate
.
labelId
,
child:
FadeTransition
(
alwaysIncludeSemantics:
true
,
opacity:
animation
,
child:
RepaintBoundary
(
key:
_labelKey
,
child:
label
,
),
),
),
],
);
},
);
}
}
/// Determines the appropriate [Curve] and [Animation] to use for laying out the
/// [NavigationDestination], based on
/// [_NavigationDestinationInfo.labelBehavior].
///
/// The animation controlling the position and fade of the labels differs
/// from the selection animation, depending on the
/// [NavigationDestinationLabelBehavior]. This widget determines what
/// animation should be used for the position and fade of the labels.
class
_DestinationLayoutAnimationBuilder
extends
StatelessWidget
{
/// Builds a child with the appropriate animation [Curve] based on the
/// [_NavigationDestinationInfo.labelBehavior].
const
_DestinationLayoutAnimationBuilder
({
Key
?
key
,
required
this
.
builder
})
:
super
(
key:
key
);
/// Builds the child of this widget.
///
/// The [Animation] will be the appropriate [Animation] to use for the layout
/// and fade of the [NavigationDestination], either a curve, always
/// showing (1), or always hiding (0).
final
Widget
Function
(
BuildContext
,
Animation
<
double
>)
builder
;
@override
Widget
build
(
BuildContext
context
)
{
final
_NavigationDestinationInfo
info
=
_NavigationDestinationInfo
.
of
(
context
);
switch
(
info
.
labelBehavior
)
{
case
NavigationDestinationLabelBehavior
.
alwaysShow
:
return
builder
(
context
,
kAlwaysCompleteAnimation
);
case
NavigationDestinationLabelBehavior
.
alwaysHide
:
return
builder
(
context
,
kAlwaysDismissedAnimation
);
case
NavigationDestinationLabelBehavior
.
onlyShowSelected
:
return
_CurvedAnimationBuilder
(
animation:
info
.
selectedAnimation
,
curve:
Curves
.
easeInOutCubicEmphasized
,
reverseCurve:
Curves
.
easeInOutCubicEmphasized
.
flipped
,
builder:
(
BuildContext
context
,
Animation
<
double
>
curvedAnimation
)
{
return
builder
(
context
,
curvedAnimation
);
},
);
}
}
}
/// Semantics widget for a navigation bar destination.
///
/// Requires a [_NavigationDestinationInfo] parent (normally provided by the
/// [NavigationBar] by default).
///
/// Provides localized semantic labels to the destination, for example, it will
/// read "Home, Tab 1 of 3".
///
/// Used by [_NavigationDestinationBuilder].
class
_NavigationBarDestinationSemantics
extends
StatelessWidget
{
/// Adds the the appropriate semantics for navigation bar destinations to the
/// [child].
const
_NavigationBarDestinationSemantics
({
Key
?
key
,
required
this
.
child
,
})
:
super
(
key:
key
);
/// The widget that should receive the destination semantics.
final
Widget
child
;
@override
Widget
build
(
BuildContext
context
)
{
final
MaterialLocalizations
localizations
=
MaterialLocalizations
.
of
(
context
);
final
_NavigationDestinationInfo
destinationInfo
=
_NavigationDestinationInfo
.
of
(
context
);
// The AnimationStatusBuilder will make sure that the semantics update to
// "selected" when the animation status changes.
return
_StatusTransitionWidgetBuilder
(
animation:
destinationInfo
.
selectedAnimation
,
builder:
(
BuildContext
context
,
Widget
?
child
)
{
return
Semantics
(
selected:
destinationInfo
.
selectedAnimation
.
isForwardOrCompleted
,
container:
true
,
child:
child
,
);
},
child:
Stack
(
alignment:
Alignment
.
center
,
children:
<
Widget
>[
child
,
Semantics
(
label:
localizations
.
tabLabel
(
tabIndex:
destinationInfo
.
index
+
1
,
tabCount:
destinationInfo
.
totalNumberOfDestinations
,
),
),
],
),
);
}
}
/// Tooltip widget for use in a [NavigationBar].
///
/// It appears just above the navigation bar when one of the destinations is
/// long pressed.
class
_NavigationBarDestinationTooltip
extends
StatelessWidget
{
/// Adds a tooltip to the [child] widget.
const
_NavigationBarDestinationTooltip
({
Key
?
key
,
required
this
.
message
,
required
this
.
child
,
})
:
super
(
key:
key
);
/// The text that is rendered in the tooltip when it appears.
///
/// If [message] is null, no tooltip will be used.
final
String
?
message
;
/// The widget that, when pressed, will show a tooltip.
final
Widget
child
;
@override
Widget
build
(
BuildContext
context
)
{
if
(
message
==
null
)
{
return
child
;
}
return
Tooltip
(
message:
message
!,
// TODO(johnsonmh): Make this value configurable/themable.
verticalOffset:
42
,
excludeFromSemantics:
true
,
preferBelow:
false
,
child:
child
,
);
}
}
/// Custom layout delegate for shifting navigation bar destinations.
///
/// This will lay out the icon + label according to the [animation].
///
/// When the [animation] is 0, the icon will be centered, and the label will be
/// positioned directly below it.
///
/// When the [animation] is 1, the label will still be positioned directly below
/// the icon, but the icon + label combination will be centered.
///
/// Used in a [CustomMultiChildLayout] widget in the
/// [_NavigationDestinationBuilder].
class
_NavigationDestinationLayoutDelegate
extends
MultiChildLayoutDelegate
{
_NavigationDestinationLayoutDelegate
({
required
this
.
animation
})
:
super
(
relayout:
animation
);
/// The selection animation that indicates whether or not this destination is
/// selected.
///
/// See [_NavigationDestinationInfo.selectedAnimation].
final
Animation
<
double
>
animation
;
/// ID for the icon widget child.
///
/// This is used by the [LayoutId] when this delegate is used in a
/// [CustomMultiChildLayout].
///
/// See [_NavigationDestinationBuilder].
static
const
int
iconId
=
1
;
/// ID for the label widget child.
///
/// This is used by the [LayoutId] when this delegate is used in a
/// [CustomMultiChildLayout].
///
/// See [_NavigationDestinationBuilder].
static
const
int
labelId
=
2
;
@override
void
performLayout
(
Size
size
)
{
double
halfWidth
(
Size
size
)
=>
size
.
width
/
2
;
double
halfHeight
(
Size
size
)
=>
size
.
height
/
2
;
final
Size
iconSize
=
layoutChild
(
iconId
,
BoxConstraints
.
loose
(
size
));
final
Size
labelSize
=
layoutChild
(
labelId
,
BoxConstraints
.
loose
(
size
));
final
double
yPositionOffset
=
Tween
<
double
>(
// When unselected, the icon is centered vertically.
begin:
halfHeight
(
iconSize
),
// When selected, the icon and label are centered vertically.
end:
halfHeight
(
iconSize
)
+
halfHeight
(
labelSize
),
).
transform
(
animation
.
value
);
final
double
iconYPosition
=
halfHeight
(
size
)
-
yPositionOffset
;
// Position the icon.
positionChild
(
iconId
,
Offset
(
// Center the icon horizontally.
halfWidth
(
size
)
-
halfWidth
(
iconSize
),
iconYPosition
,
),
);
// Position the label.
positionChild
(
labelId
,
Offset
(
// Center the label horizontally.
halfWidth
(
size
)
-
halfWidth
(
labelSize
),
// Label always appears directly below the icon.
iconYPosition
+
iconSize
.
height
,
),
);
}
@override
bool
shouldRelayout
(
_NavigationDestinationLayoutDelegate
oldDelegate
)
{
return
oldDelegate
.
animation
!=
animation
;
}
}
/// Utility Widgets
/// Clamps [MediaQueryData.textScaleFactor] so that if it is greater than
/// [upperLimit] or less than [lowerLimit], [upperLimit] or [lowerLimit] will be
/// used instead for the [child] widget.
///
/// Example:
/// ```
/// _ClampTextScaleFactor(
/// upperLimit: 2.0,
/// child: Text('Foo'), // If textScaleFactor is 3.0, this will only scale 2x.
/// )
/// ```
class
_ClampTextScaleFactor
extends
StatelessWidget
{
/// Clamps the text scale factor of descendants by modifying the [MediaQuery]
/// surrounding [child].
const
_ClampTextScaleFactor
({
Key
?
key
,
this
.
lowerLimit
=
0
,
this
.
upperLimit
=
double
.
infinity
,
required
this
.
child
,
})
:
super
(
key:
key
);
/// The minimum amount that the text scale factor should be for the [child]
/// widget.
///
/// If this is `.5`, the textScaleFactor for child widgets will never be
/// smaller than `.5`.
final
double
lowerLimit
;
/// The maximum amount that the text scale factor should be for the [child]
/// widget.
///
/// If this is `1.5`, the textScaleFactor for child widgets will never be
/// greater than `1.5`.
final
double
upperLimit
;
/// The [Widget] that should have its (and its descendants) text scale factor
/// clamped.
final
Widget
child
;
@override
Widget
build
(
BuildContext
context
)
{
return
MediaQuery
(
data:
MediaQuery
.
of
(
context
).
copyWith
(
textScaleFactor:
MediaQuery
.
of
(
context
).
textScaleFactor
.
clamp
(
lowerLimit
,
upperLimit
,
),
),
child:
child
,
);
}
}
/// Widget that listens to an animation, and rebuilds when the animation changes
/// [AnimationStatus].
///
/// This can be more efficient than just using an [AnimatedBuilder] when you
/// only need to rebuild when the [Animation.status] changes, since
/// [AnimatedBuilder] rebuilds every time the animation ticks.
class
_StatusTransitionWidgetBuilder
extends
StatusTransitionWidget
{
/// Creates a widget that rebuilds when the given animation changes status.
const
_StatusTransitionWidgetBuilder
({
Key
?
key
,
required
Animation
<
double
>
animation
,
required
this
.
builder
,
this
.
child
,
})
:
super
(
animation:
animation
,
key:
key
);
/// Called every time the [animation] changes [AnimationStatus].
final
TransitionBuilder
builder
;
/// The child widget to pass to the [builder].
///
/// If a [builder] callback's return value contains a subtree that does not
/// depend on the animation, it's more efficient to build that subtree once
/// instead of rebuilding it on every animation status change.
///
/// Using this pre-built child is entirely optional, but can improve
/// performance in some cases and is therefore a good practice.
///
/// See: [AnimatedBuilder.child]
final
Widget
?
child
;
@override
Widget
build
(
BuildContext
context
)
=>
builder
(
context
,
child
);
}
/// Builder widget for widgets that need to be animated from 0 (unselected) to
/// 1.0 (selected).
///
/// This widget creates and manages an [AnimationController] that it passes down
/// to the child through the [builder] function.
///
/// When [isSelected] is `true`, the animation controller will animate from
/// 0 to 1 (for [duration] time).
///
/// When [isSelected] is `false`, the animation controller will animate from
/// 1 to 0 (for [duration] time).
///
/// If [isSelected] is updated while the widget is animating, the animation will
/// be reversed until it is either 0 or 1 again. If [alwaysDoFullAnimation] is
/// true, the animation will reset to 0 or 1 before beginning the animation, so
/// that the full animation is done.
///
/// Usage:
/// ```dart
/// _SelectableAnimatedBuilder(
/// isSelected: _isDrawerOpen,
/// builder: (context, animation) {
/// return AnimatedIcon(
/// icon: AnimatedIcons.menu_arrow,
/// progress: animation,
/// semanticLabel: 'Show menu',
/// );
/// }
/// )
/// ```
class
_SelectableAnimatedBuilder
extends
StatefulWidget
{
/// Builds and maintains an [AnimationController] that will animate from 0 to
/// 1 and back depending on when [isSelected] is true.
const
_SelectableAnimatedBuilder
({
Key
?
key
,
required
this
.
isSelected
,
this
.
duration
=
const
Duration
(
milliseconds:
200
),
this
.
alwaysDoFullAnimation
=
false
,
required
this
.
builder
,
})
:
super
(
key:
key
);
/// When true, the widget will animate an animation controller from 0 to 1.
///
/// The animation controller is passed to the child widget through [builder].
final
bool
isSelected
;
/// How long the animation controller should animate for when [isSelected] is
/// updated.
///
/// If the animation is currently running and [isSelected] is updated, only
/// the [duration] left to finish the animation will be run.
final
Duration
duration
;
/// If true, the animation will always go all the way from 0 to 1 when
/// [isSelected] is true, and from 1 to 0 when [isSelected] is false, even
/// when the status changes mid animation.
///
/// If this is false and the status changes mid animation, the animation will
/// reverse direction from it's current point.
///
/// Defaults to false.
final
bool
alwaysDoFullAnimation
;
/// Builds the child widget based on the current animation status.
///
/// When [isSelected] is updated to true, this builder will be called and the
/// animation will animate up to 1. When [isSelected] is updated to
/// `false`, this will be called and the animation will animate down to 0.
final
Widget
Function
(
BuildContext
,
Animation
<
double
>)
builder
;
@override
_SelectableAnimatedBuilderState
createState
()
=>
_SelectableAnimatedBuilderState
();
}
/// State that manages the [AnimationController] that is passed to
/// [_SelectableAnimatedBuilder.builder].
class
_SelectableAnimatedBuilderState
extends
State
<
_SelectableAnimatedBuilder
>
with
SingleTickerProviderStateMixin
{
late
AnimationController
_controller
;
@override
void
initState
()
{
super
.
initState
();
_controller
=
AnimationController
(
vsync:
this
);
_controller
.
duration
=
widget
.
duration
;
_controller
.
value
=
widget
.
isSelected
?
1.0
:
0.0
;
}
@override
void
didUpdateWidget
(
_SelectableAnimatedBuilder
oldWidget
)
{
super
.
didUpdateWidget
(
oldWidget
);
if
(
oldWidget
.
duration
!=
widget
.
duration
)
{
_controller
.
duration
=
widget
.
duration
;
}
if
(
oldWidget
.
isSelected
!=
widget
.
isSelected
)
{
if
(
widget
.
isSelected
)
{
_controller
.
forward
(
from:
widget
.
alwaysDoFullAnimation
?
0
:
null
);
}
else
{
_controller
.
reverse
(
from:
widget
.
alwaysDoFullAnimation
?
1
:
null
);
}
}
}
@override
void
dispose
()
{
_controller
.
dispose
();
super
.
dispose
();
}
@override
Widget
build
(
BuildContext
context
)
{
return
widget
.
builder
(
context
,
_controller
,
);
}
}
/// Watches [animation] and calls [builder] with the appropriate [Curve]
/// depending on the direction of the [animation] status.
///
/// If [animation.status] is forward or complete, [curve] is used. If
/// [animation.status] is reverse or dismissed, [reverseCurve] is used.
///
/// If the [animation] changes direction while it is already running, the curve
/// used will not change, this will keep the animations smooth until it
/// completes.
///
/// This is similar to [CurvedAnimation] except the animation status listeners
/// are removed when this widget is disposed.
class
_CurvedAnimationBuilder
extends
StatefulWidget
{
const
_CurvedAnimationBuilder
({
Key
?
key
,
required
this
.
animation
,
required
this
.
curve
,
required
this
.
reverseCurve
,
required
this
.
builder
,
})
:
super
(
key:
key
);
final
Animation
<
double
>
animation
;
final
Curve
curve
;
final
Curve
reverseCurve
;
final
Widget
Function
(
BuildContext
,
Animation
<
double
>)
builder
;
@override
_CurvedAnimationBuilderState
createState
()
=>
_CurvedAnimationBuilderState
();
}
class
_CurvedAnimationBuilderState
extends
State
<
_CurvedAnimationBuilder
>
{
late
AnimationStatus
_animationDirection
;
AnimationStatus
?
_preservedDirection
;
@override
void
initState
()
{
super
.
initState
();
_animationDirection
=
widget
.
animation
.
status
;
_updateStatus
(
widget
.
animation
.
status
);
widget
.
animation
.
addStatusListener
(
_updateStatus
);
}
@override
void
dispose
()
{
widget
.
animation
.
removeStatusListener
(
_updateStatus
);
super
.
dispose
();
}
// Keeps track of the current animation status, as well as the "preserved
// direction" when the animation changes direction mid animation.
//
// The preserved direction is reset when the animation finishes in either
// direction.
void
_updateStatus
(
AnimationStatus
status
)
{
if
(
_animationDirection
!=
status
)
{
setState
(()
{
_animationDirection
=
status
;
});
}
if
(
status
==
AnimationStatus
.
completed
||
status
==
AnimationStatus
.
dismissed
)
{
setState
(()
{
_preservedDirection
=
null
;
});
}
if
(
_preservedDirection
==
null
&&
(
status
==
AnimationStatus
.
forward
||
status
==
AnimationStatus
.
reverse
))
{
setState
(()
{
_preservedDirection
=
status
;
});
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
bool
shouldUseForwardCurve
=
(
_preservedDirection
??
_animationDirection
)
!=
AnimationStatus
.
reverse
;
final
Animation
<
double
>
curvedAnimation
=
CurveTween
(
curve:
shouldUseForwardCurve
?
widget
.
curve
:
widget
.
reverseCurve
,
).
animate
(
widget
.
animation
);
return
widget
.
builder
(
context
,
curvedAnimation
);
}
}
/// Convenience static extensions on Animation.
extension
_AnimationUtils
on
Animation
<
double
>
{
/// Returns `true` if this animation is ticking forward, or has completed,
/// based on [status].
bool
get
isForwardOrCompleted
=>
status
==
AnimationStatus
.
forward
||
status
==
AnimationStatus
.
completed
;
}
packages/flutter/lib/src/material/navigation_bar_theme.dart
0 → 100644
View file @
f3049c77
// 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/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'material_state.dart'
;
import
'navigation_bar.dart'
;
import
'theme.dart'
;
/// Defines default property values for descendant [NavigationBar]
/// widgets.
///
/// Descendant widgets obtain the current [NavigationBarThemeData] object
/// using `NavigationBarTheme.of(context)`. Instances of
/// [NavigationBarThemeData] can be customized with
/// [NavigationBarThemeData.copyWith].
///
/// Typically a [NavigationBarThemeData] is specified as part of the
/// overall [Theme] with [ThemeData.navigationBarTheme]. Alternatively, a
/// [NavigationBarTheme] inherited widget can be used to theme [NavigationBar]s
/// in a subtree of widgets.
///
/// All [NavigationBarThemeData] properties are `null` by default.
/// When null, the [NavigationBar] will provide its own defaults based on the
/// overall [Theme]'s textTheme and colorScheme. See the individual
/// [NavigationBar] properties for details.
///
/// See also:
///
/// * [ThemeData], which describes the overall theme information for the
/// application.
@immutable
class
NavigationBarThemeData
with
Diagnosticable
{
/// Creates a theme that can be used for [ThemeData.navigationBarTheme] and
/// [NavigationBarTheme].
const
NavigationBarThemeData
({
this
.
height
,
this
.
backgroundColor
,
this
.
indicatorColor
,
this
.
labelTextStyle
,
this
.
iconTheme
,
this
.
labelBehavior
,
});
/// Overrides the default value of [NavigationBar.height].
final
double
?
height
;
/// Overrides the default value of [NavigationBar.backgroundColor].
final
Color
?
backgroundColor
;
/// Overrides the default value of [NavigationBar]'s selection indicator.
final
Color
?
indicatorColor
;
/// The style to merge with the default text style for
/// [NavigationDestination] labels.
///
/// You can use this to specify a different style when the label is selected.
final
MaterialStateProperty
<
TextStyle
?>?
labelTextStyle
;
/// The theme to merge with the default icon theme for
/// [NavigationDestination] icons.
///
/// You can use this to specify a different icon theme when the icon is
/// selected.
final
MaterialStateProperty
<
IconThemeData
?>?
iconTheme
;
/// Overrides the default value of [NavigationBar.labelBehavior].
final
NavigationDestinationLabelBehavior
?
labelBehavior
;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
NavigationBarThemeData
copyWith
({
double
?
height
,
Color
?
backgroundColor
,
Color
?
indicatorColor
,
MaterialStateProperty
<
TextStyle
?>?
labelTextStyle
,
MaterialStateProperty
<
IconThemeData
?>?
iconTheme
,
NavigationDestinationLabelBehavior
?
labelBehavior
,
})
{
return
NavigationBarThemeData
(
height:
height
??
this
.
height
,
backgroundColor:
backgroundColor
??
this
.
backgroundColor
,
indicatorColor:
indicatorColor
??
this
.
indicatorColor
,
labelTextStyle:
labelTextStyle
??
this
.
labelTextStyle
,
iconTheme:
iconTheme
??
this
.
iconTheme
,
labelBehavior:
labelBehavior
??
this
.
labelBehavior
,
);
}
/// Linearly interpolate between two navigation rail themes.
///
/// If both arguments are null then null is returned.
///
/// {@macro dart.ui.shadow.lerp}
static
NavigationBarThemeData
?
lerp
(
NavigationBarThemeData
?
a
,
NavigationBarThemeData
?
b
,
double
t
)
{
assert
(
t
!=
null
);
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
NavigationBarThemeData
(
height:
lerpDouble
(
a
?.
height
,
b
?.
height
,
t
),
backgroundColor:
Color
.
lerp
(
a
?.
backgroundColor
,
b
?.
backgroundColor
,
t
),
indicatorColor:
Color
.
lerp
(
a
?.
indicatorColor
,
b
?.
indicatorColor
,
t
),
labelTextStyle:
_lerpProperties
<
TextStyle
?>(
a
?.
labelTextStyle
,
b
?.
labelTextStyle
,
t
,
TextStyle
.
lerp
),
iconTheme:
_lerpProperties
<
IconThemeData
?>(
a
?.
iconTheme
,
b
?.
iconTheme
,
t
,
IconThemeData
.
lerp
),
labelBehavior:
t
<
0.5
?
a
?.
labelBehavior
:
b
?.
labelBehavior
,
);
}
@override
int
get
hashCode
{
return
hashValues
(
height
,
backgroundColor
,
indicatorColor
,
labelTextStyle
,
iconTheme
,
labelBehavior
,
);
}
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
return
true
;
if
(
other
.
runtimeType
!=
runtimeType
)
return
false
;
return
other
is
NavigationBarThemeData
&&
other
.
height
==
height
&&
other
.
backgroundColor
==
backgroundColor
&&
other
.
indicatorColor
==
indicatorColor
&&
other
.
labelTextStyle
==
labelTextStyle
&&
other
.
iconTheme
==
iconTheme
&&
other
.
labelBehavior
==
labelBehavior
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DoubleProperty
(
'height'
,
height
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'backgroundColor'
,
backgroundColor
,
defaultValue:
null
));
properties
.
add
(
ColorProperty
(
'indicatorColor'
,
indicatorColor
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
TextStyle
?>>(
'labelTextStyle'
,
labelTextStyle
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
MaterialStateProperty
<
IconThemeData
?>>(
'iconTheme'
,
iconTheme
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
NavigationDestinationLabelBehavior
>(
'labelBehavior'
,
labelBehavior
,
defaultValue:
null
));
}
static
MaterialStateProperty
<
T
>?
_lerpProperties
<
T
>(
MaterialStateProperty
<
T
>?
a
,
MaterialStateProperty
<
T
>?
b
,
double
t
,
T
Function
(
T
?,
T
?,
double
)
lerpFunction
,
)
{
// Avoid creating a _LerpProperties object for a common case.
if
(
a
==
null
&&
b
==
null
)
return
null
;
return
_LerpProperties
<
T
>(
a
,
b
,
t
,
lerpFunction
);
}
}
class
_LerpProperties
<
T
>
implements
MaterialStateProperty
<
T
>
{
const
_LerpProperties
(
this
.
a
,
this
.
b
,
this
.
t
,
this
.
lerpFunction
);
final
MaterialStateProperty
<
T
>?
a
;
final
MaterialStateProperty
<
T
>?
b
;
final
double
t
;
final
T
Function
(
T
?,
T
?,
double
)
lerpFunction
;
@override
T
resolve
(
Set
<
MaterialState
>
states
)
{
final
T
?
resolvedA
=
a
?.
resolve
(
states
);
final
T
?
resolvedB
=
b
?.
resolve
(
states
);
return
lerpFunction
(
resolvedA
,
resolvedB
,
t
);
}
}
/// An inherited widget that defines visual properties for [NavigationBar]s and
/// [NavigationDestination]s in this widget's subtree.
///
/// Values specified here are used for [NavigationBar] properties that are not
/// given an explicit non-null value.
///
/// See also:
///
/// * [ThemeData.navigationBarTheme], which describes the
/// [NavigationBarThemeData] in the overall theme for the application.
class
NavigationBarTheme
extends
InheritedTheme
{
/// Creates a navigation rail theme that controls the
/// [NavigationBarThemeData] properties for a [NavigationBar].
///
/// The data argument must not be null.
const
NavigationBarTheme
({
Key
?
key
,
required
this
.
data
,
required
Widget
child
,
})
:
assert
(
data
!=
null
),
super
(
key:
key
,
child:
child
);
/// Specifies the background color, label text style, icon theme, and label
/// type values for descendant [NavigationBar] widgets.
final
NavigationBarThemeData
data
;
/// The closest instance of this class that encloses the given context.
///
/// If there is no enclosing [NavigationBarTheme] widget, then
/// [ThemeData.navigationBarTheme] is used.
///
/// Typical usage is as follows:
///
/// ```dart
/// NavigationBarTheme theme = NavigationBarTheme.of(context);
/// ```
static
NavigationBarThemeData
of
(
BuildContext
context
)
{
final
NavigationBarTheme
?
navigationBarTheme
=
context
.
dependOnInheritedWidgetOfExactType
<
NavigationBarTheme
>();
return
navigationBarTheme
?.
data
??
Theme
.
of
(
context
).
navigationBarTheme
;
}
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
)
{
return
NavigationBarTheme
(
data:
data
,
child:
child
);
}
@override
bool
updateShouldNotify
(
NavigationBarTheme
oldWidget
)
=>
data
!=
oldWidget
.
data
;
}
packages/flutter/lib/src/material/theme_data.dart
View file @
f3049c77
...
@@ -28,6 +28,7 @@ import 'floating_action_button_theme.dart';
...
@@ -28,6 +28,7 @@ import 'floating_action_button_theme.dart';
import
'ink_splash.dart'
;
import
'ink_splash.dart'
;
import
'ink_well.dart'
show
InteractiveInkFeatureFactory
;
import
'ink_well.dart'
show
InteractiveInkFeatureFactory
;
import
'input_decorator.dart'
;
import
'input_decorator.dart'
;
import
'navigation_bar_theme.dart'
;
import
'navigation_rail_theme.dart'
;
import
'navigation_rail_theme.dart'
;
import
'outlined_button_theme.dart'
;
import
'outlined_button_theme.dart'
;
import
'page_transitions_theme.dart'
;
import
'page_transitions_theme.dart'
;
...
@@ -317,6 +318,7 @@ class ThemeData with Diagnosticable {
...
@@ -317,6 +318,7 @@ class ThemeData with Diagnosticable {
ColorScheme
?
colorScheme
,
ColorScheme
?
colorScheme
,
DialogTheme
?
dialogTheme
,
DialogTheme
?
dialogTheme
,
FloatingActionButtonThemeData
?
floatingActionButtonTheme
,
FloatingActionButtonThemeData
?
floatingActionButtonTheme
,
NavigationBarThemeData
?
navigationBarTheme
,
NavigationRailThemeData
?
navigationRailTheme
,
NavigationRailThemeData
?
navigationRailTheme
,
Typography
?
typography
,
Typography
?
typography
,
NoDefaultCupertinoThemeData
?
cupertinoOverrideTheme
,
NoDefaultCupertinoThemeData
?
cupertinoOverrideTheme
,
...
@@ -462,6 +464,7 @@ class ThemeData with Diagnosticable {
...
@@ -462,6 +464,7 @@ class ThemeData with Diagnosticable {
);
);
dialogTheme
??=
const
DialogTheme
();
dialogTheme
??=
const
DialogTheme
();
floatingActionButtonTheme
??=
const
FloatingActionButtonThemeData
();
floatingActionButtonTheme
??=
const
FloatingActionButtonThemeData
();
navigationBarTheme
??=
const
NavigationBarThemeData
();
navigationRailTheme
??=
const
NavigationRailThemeData
();
navigationRailTheme
??=
const
NavigationRailThemeData
();
cupertinoOverrideTheme
=
cupertinoOverrideTheme
?.
noDefault
();
cupertinoOverrideTheme
=
cupertinoOverrideTheme
?.
noDefault
();
snackBarTheme
??=
const
SnackBarThemeData
();
snackBarTheme
??=
const
SnackBarThemeData
();
...
@@ -543,6 +546,7 @@ class ThemeData with Diagnosticable {
...
@@ -543,6 +546,7 @@ class ThemeData with Diagnosticable {
colorScheme:
colorScheme
,
colorScheme:
colorScheme
,
dialogTheme:
dialogTheme
,
dialogTheme:
dialogTheme
,
floatingActionButtonTheme:
floatingActionButtonTheme
,
floatingActionButtonTheme:
floatingActionButtonTheme
,
navigationBarTheme:
navigationBarTheme
,
navigationRailTheme:
navigationRailTheme
,
navigationRailTheme:
navigationRailTheme
,
typography:
typography
,
typography:
typography
,
cupertinoOverrideTheme:
cupertinoOverrideTheme
,
cupertinoOverrideTheme:
cupertinoOverrideTheme
,
...
@@ -677,6 +681,7 @@ class ThemeData with Diagnosticable {
...
@@ -677,6 +681,7 @@ class ThemeData with Diagnosticable {
required
this
.
colorScheme
,
required
this
.
colorScheme
,
required
this
.
dialogTheme
,
required
this
.
dialogTheme
,
required
this
.
floatingActionButtonTheme
,
required
this
.
floatingActionButtonTheme
,
required
this
.
navigationBarTheme
,
required
this
.
navigationRailTheme
,
required
this
.
navigationRailTheme
,
required
this
.
typography
,
required
this
.
typography
,
required
this
.
cupertinoOverrideTheme
,
required
this
.
cupertinoOverrideTheme
,
...
@@ -763,6 +768,7 @@ class ThemeData with Diagnosticable {
...
@@ -763,6 +768,7 @@ class ThemeData with Diagnosticable {
assert
(
colorScheme
!=
null
),
assert
(
colorScheme
!=
null
),
assert
(
dialogTheme
!=
null
),
assert
(
dialogTheme
!=
null
),
assert
(
floatingActionButtonTheme
!=
null
),
assert
(
floatingActionButtonTheme
!=
null
),
assert
(
navigationBarTheme
!=
null
),
assert
(
navigationRailTheme
!=
null
),
assert
(
navigationRailTheme
!=
null
),
assert
(
typography
!=
null
),
assert
(
typography
!=
null
),
assert
(
snackBarTheme
!=
null
),
assert
(
snackBarTheme
!=
null
),
...
@@ -1269,6 +1275,10 @@ class ThemeData with Diagnosticable {
...
@@ -1269,6 +1275,10 @@ class ThemeData with Diagnosticable {
/// [FloatingActionButton].
/// [FloatingActionButton].
final
FloatingActionButtonThemeData
floatingActionButtonTheme
;
final
FloatingActionButtonThemeData
floatingActionButtonTheme
;
/// A theme for customizing the background color, text style, and icon themes
/// of a [NavigationBar].
final
NavigationBarThemeData
navigationBarTheme
;
/// A theme for customizing the background color, elevation, text style, and
/// A theme for customizing the background color, elevation, text style, and
/// icon themes of a [NavigationRail].
/// icon themes of a [NavigationRail].
final
NavigationRailThemeData
navigationRailTheme
;
final
NavigationRailThemeData
navigationRailTheme
;
...
@@ -1485,6 +1495,7 @@ class ThemeData with Diagnosticable {
...
@@ -1485,6 +1495,7 @@ class ThemeData with Diagnosticable {
ColorScheme
?
colorScheme
,
ColorScheme
?
colorScheme
,
DialogTheme
?
dialogTheme
,
DialogTheme
?
dialogTheme
,
FloatingActionButtonThemeData
?
floatingActionButtonTheme
,
FloatingActionButtonThemeData
?
floatingActionButtonTheme
,
NavigationBarThemeData
?
navigationBarTheme
,
NavigationRailThemeData
?
navigationRailTheme
,
NavigationRailThemeData
?
navigationRailTheme
,
Typography
?
typography
,
Typography
?
typography
,
NoDefaultCupertinoThemeData
?
cupertinoOverrideTheme
,
NoDefaultCupertinoThemeData
?
cupertinoOverrideTheme
,
...
@@ -1576,6 +1587,7 @@ class ThemeData with Diagnosticable {
...
@@ -1576,6 +1587,7 @@ class ThemeData with Diagnosticable {
colorScheme:
(
colorScheme
??
this
.
colorScheme
).
copyWith
(
brightness:
brightness
),
colorScheme:
(
colorScheme
??
this
.
colorScheme
).
copyWith
(
brightness:
brightness
),
dialogTheme:
dialogTheme
??
this
.
dialogTheme
,
dialogTheme:
dialogTheme
??
this
.
dialogTheme
,
floatingActionButtonTheme:
floatingActionButtonTheme
??
this
.
floatingActionButtonTheme
,
floatingActionButtonTheme:
floatingActionButtonTheme
??
this
.
floatingActionButtonTheme
,
navigationBarTheme:
navigationBarTheme
??
this
.
navigationBarTheme
,
navigationRailTheme:
navigationRailTheme
??
this
.
navigationRailTheme
,
navigationRailTheme:
navigationRailTheme
??
this
.
navigationRailTheme
,
typography:
typography
??
this
.
typography
,
typography:
typography
??
this
.
typography
,
cupertinoOverrideTheme:
cupertinoOverrideTheme
??
this
.
cupertinoOverrideTheme
,
cupertinoOverrideTheme:
cupertinoOverrideTheme
??
this
.
cupertinoOverrideTheme
,
...
@@ -1737,6 +1749,7 @@ class ThemeData with Diagnosticable {
...
@@ -1737,6 +1749,7 @@ class ThemeData with Diagnosticable {
colorScheme:
ColorScheme
.
lerp
(
a
.
colorScheme
,
b
.
colorScheme
,
t
),
colorScheme:
ColorScheme
.
lerp
(
a
.
colorScheme
,
b
.
colorScheme
,
t
),
dialogTheme:
DialogTheme
.
lerp
(
a
.
dialogTheme
,
b
.
dialogTheme
,
t
),
dialogTheme:
DialogTheme
.
lerp
(
a
.
dialogTheme
,
b
.
dialogTheme
,
t
),
floatingActionButtonTheme:
FloatingActionButtonThemeData
.
lerp
(
a
.
floatingActionButtonTheme
,
b
.
floatingActionButtonTheme
,
t
)!,
floatingActionButtonTheme:
FloatingActionButtonThemeData
.
lerp
(
a
.
floatingActionButtonTheme
,
b
.
floatingActionButtonTheme
,
t
)!,
navigationBarTheme:
NavigationBarThemeData
.
lerp
(
a
.
navigationBarTheme
,
b
.
navigationBarTheme
,
t
)!,
navigationRailTheme:
NavigationRailThemeData
.
lerp
(
a
.
navigationRailTheme
,
b
.
navigationRailTheme
,
t
)!,
navigationRailTheme:
NavigationRailThemeData
.
lerp
(
a
.
navigationRailTheme
,
b
.
navigationRailTheme
,
t
)!,
typography:
Typography
.
lerp
(
a
.
typography
,
b
.
typography
,
t
),
typography:
Typography
.
lerp
(
a
.
typography
,
b
.
typography
,
t
),
cupertinoOverrideTheme:
t
<
0.5
?
a
.
cupertinoOverrideTheme
:
b
.
cupertinoOverrideTheme
,
cupertinoOverrideTheme:
t
<
0.5
?
a
.
cupertinoOverrideTheme
:
b
.
cupertinoOverrideTheme
,
...
@@ -1826,6 +1839,7 @@ class ThemeData with Diagnosticable {
...
@@ -1826,6 +1839,7 @@ class ThemeData with Diagnosticable {
&&
other
.
colorScheme
==
colorScheme
&&
other
.
colorScheme
==
colorScheme
&&
other
.
dialogTheme
==
dialogTheme
&&
other
.
dialogTheme
==
dialogTheme
&&
other
.
floatingActionButtonTheme
==
floatingActionButtonTheme
&&
other
.
floatingActionButtonTheme
==
floatingActionButtonTheme
&&
other
.
navigationBarTheme
==
navigationBarTheme
&&
other
.
navigationRailTheme
==
navigationRailTheme
&&
other
.
navigationRailTheme
==
navigationRailTheme
&&
other
.
typography
==
typography
&&
other
.
typography
==
typography
&&
other
.
cupertinoOverrideTheme
==
cupertinoOverrideTheme
&&
other
.
cupertinoOverrideTheme
==
cupertinoOverrideTheme
...
@@ -1914,6 +1928,7 @@ class ThemeData with Diagnosticable {
...
@@ -1914,6 +1928,7 @@ class ThemeData with Diagnosticable {
colorScheme
,
colorScheme
,
dialogTheme
,
dialogTheme
,
floatingActionButtonTheme
,
floatingActionButtonTheme
,
navigationBarTheme
,
navigationRailTheme
,
navigationRailTheme
,
typography
,
typography
,
cupertinoOverrideTheme
,
cupertinoOverrideTheme
,
...
@@ -1999,6 +2014,7 @@ class ThemeData with Diagnosticable {
...
@@ -1999,6 +2014,7 @@ class ThemeData with Diagnosticable {
properties
.
add
(
DiagnosticsProperty
<
ColorScheme
>(
'colorScheme'
,
colorScheme
,
defaultValue:
defaultData
.
colorScheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ColorScheme
>(
'colorScheme'
,
colorScheme
,
defaultValue:
defaultData
.
colorScheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
DialogTheme
>(
'dialogTheme'
,
dialogTheme
,
defaultValue:
defaultData
.
dialogTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
DialogTheme
>(
'dialogTheme'
,
dialogTheme
,
defaultValue:
defaultData
.
dialogTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
FloatingActionButtonThemeData
>(
'floatingActionButtonThemeData'
,
floatingActionButtonTheme
,
defaultValue:
defaultData
.
floatingActionButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
FloatingActionButtonThemeData
>(
'floatingActionButtonThemeData'
,
floatingActionButtonTheme
,
defaultValue:
defaultData
.
floatingActionButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
NavigationBarThemeData
>(
'navigationBarTheme'
,
navigationBarTheme
,
defaultValue:
defaultData
.
navigationBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
NavigationRailThemeData
>(
'navigationRailThemeData'
,
navigationRailTheme
,
defaultValue:
defaultData
.
navigationRailTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
NavigationRailThemeData
>(
'navigationRailThemeData'
,
navigationRailTheme
,
defaultValue:
defaultData
.
navigationRailTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
Typography
>(
'typography'
,
typography
,
defaultValue:
defaultData
.
typography
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
Typography
>(
'typography'
,
typography
,
defaultValue:
defaultData
.
typography
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
NoDefaultCupertinoThemeData
>(
'cupertinoOverrideTheme'
,
cupertinoOverrideTheme
,
defaultValue:
defaultData
.
cupertinoOverrideTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
NoDefaultCupertinoThemeData
>(
'cupertinoOverrideTheme'
,
cupertinoOverrideTheme
,
defaultValue:
defaultData
.
cupertinoOverrideTheme
,
level:
DiagnosticLevel
.
debug
));
...
...
packages/flutter/test/material/navigation_bar_test.dart
0 → 100644
View file @
f3049c77
// 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_test/flutter_test.dart'
;
void
main
(
)
{
testWidgets
(
'Navigation bar updates destinations when tapped'
,
(
WidgetTester
tester
)
async
{
int
mutatedIndex
=
-
1
;
final
Widget
widget
=
_buildWidget
(
NavigationBar
(
destinations:
const
<
Widget
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
ac_unit
),
label:
'AC'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
access_alarm
),
label:
'Alarm'
,
),
],
onDestinationSelected:
(
int
i
)
{
mutatedIndex
=
i
;
},
),
);
await
tester
.
pumpWidget
(
widget
);
expect
(
find
.
text
(
'AC'
),
findsOneWidget
);
expect
(
find
.
text
(
'Alarm'
),
findsOneWidget
);
await
tester
.
tap
(
find
.
text
(
'Alarm'
));
expect
(
mutatedIndex
,
1
);
await
tester
.
tap
(
find
.
text
(
'AC'
));
expect
(
mutatedIndex
,
0
);
});
testWidgets
(
'NavigationBar can update background color'
,
(
WidgetTester
tester
)
async
{
const
Color
color
=
Colors
.
yellow
;
await
tester
.
pumpWidget
(
_buildWidget
(
NavigationBar
(
backgroundColor:
color
,
destinations:
const
<
Widget
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
ac_unit
),
label:
'AC'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
access_alarm
),
label:
'Alarm'
,
),
],
onDestinationSelected:
(
int
i
)
{},
),
),
);
expect
(
_getMaterial
(
tester
).
color
,
equals
(
color
));
});
testWidgets
(
'NavigationBar adds bottom padding to height'
,
(
WidgetTester
tester
)
async
{
const
double
bottomPadding
=
40.0
;
await
tester
.
pumpWidget
(
_buildWidget
(
NavigationBar
(
destinations:
const
<
Widget
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
ac_unit
),
label:
'AC'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
access_alarm
),
label:
'Alarm'
,
),
],
onDestinationSelected:
(
int
i
)
{},
),
),
);
final
double
defaultSize
=
tester
.
getSize
(
find
.
byType
(
NavigationBar
)).
height
;
expect
(
defaultSize
,
80
);
await
tester
.
pumpWidget
(
_buildWidget
(
MediaQuery
(
data:
const
MediaQueryData
(
padding:
EdgeInsets
.
only
(
bottom:
bottomPadding
)),
child:
NavigationBar
(
destinations:
const
<
Widget
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
ac_unit
),
label:
'AC'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
access_alarm
),
label:
'Alarm'
,
),
],
onDestinationSelected:
(
int
i
)
{},
),
),
),
);
final
double
expectedHeight
=
defaultSize
+
bottomPadding
;
expect
(
tester
.
getSize
(
find
.
byType
(
NavigationBar
)).
height
,
expectedHeight
);
});
testWidgets
(
'NavigationBar shows tooltips with text scaling '
,
(
WidgetTester
tester
)
async
{
const
String
label
=
'A'
;
Widget
buildApp
({
required
double
textScaleFactor
})
{
return
MediaQuery
(
data:
MediaQueryData
(
textScaleFactor:
textScaleFactor
),
child:
Localizations
(
locale:
const
Locale
(
'en'
,
'US'
),
delegates:
const
<
LocalizationsDelegate
<
dynamic
>>[
DefaultMaterialLocalizations
.
delegate
,
DefaultWidgetsLocalizations
.
delegate
,
],
child:
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Navigator
(
onGenerateRoute:
(
RouteSettings
settings
)
{
return
MaterialPageRoute
<
void
>(
builder:
(
BuildContext
context
)
{
return
Scaffold
(
bottomNavigationBar:
NavigationBar
(
destinations:
const
<
NavigationDestination
>[
NavigationDestination
(
label:
label
,
icon:
Icon
(
Icons
.
ac_unit
),
tooltip:
label
,
),
NavigationDestination
(
label:
'B'
,
icon:
Icon
(
Icons
.
battery_alert
),
),
],
),
);
},
);
},
),
),
),
);
}
await
tester
.
pumpWidget
(
buildApp
(
textScaleFactor:
1.0
));
expect
(
find
.
text
(
label
),
findsOneWidget
);
await
tester
.
longPress
(
find
.
text
(
label
));
expect
(
find
.
text
(
label
),
findsNWidgets
(
2
));
// The default size of a tooltip with the text A.
const
Size
defaultTooltipSize
=
Size
(
14.0
,
14.0
);
expect
(
tester
.
getSize
(
find
.
text
(
label
).
last
),
defaultTooltipSize
);
// The duration is needed to ensure the tooltip disappears.
await
tester
.
pumpAndSettle
(
const
Duration
(
seconds:
2
));
await
tester
.
pumpWidget
(
buildApp
(
textScaleFactor:
4.0
));
expect
(
find
.
text
(
label
),
findsOneWidget
);
await
tester
.
longPress
(
find
.
text
(
label
));
expect
(
tester
.
getSize
(
find
.
text
(
label
).
last
),
Size
(
defaultTooltipSize
.
width
*
4
,
defaultTooltipSize
.
height
*
4
));
});
testWidgets
(
'Custom tooltips in NavigationBarDestination'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
bottomNavigationBar:
NavigationBar
(
destinations:
const
<
NavigationDestination
>[
NavigationDestination
(
label:
'A'
,
tooltip:
'A tooltip'
,
icon:
Icon
(
Icons
.
ac_unit
),
),
NavigationDestination
(
label:
'B'
,
icon:
Icon
(
Icons
.
battery_alert
),
),
NavigationDestination
(
label:
'C'
,
icon:
Icon
(
Icons
.
cake
),
tooltip:
''
,
),
],
),
),
),
);
expect
(
find
.
text
(
'A'
),
findsOneWidget
);
await
tester
.
longPress
(
find
.
text
(
'A'
));
expect
(
find
.
byTooltip
(
'A tooltip'
),
findsOneWidget
);
expect
(
find
.
text
(
'B'
),
findsOneWidget
);
await
tester
.
longPress
(
find
.
text
(
'B'
));
expect
(
find
.
byTooltip
(
'B'
),
findsOneWidget
);
expect
(
find
.
text
(
'C'
),
findsOneWidget
);
await
tester
.
longPress
(
find
.
text
(
'C'
));
expect
(
find
.
byTooltip
(
'C'
),
findsNothing
);
});
testWidgets
(
'Navigation bar semantics'
,
(
WidgetTester
tester
)
async
{
Widget
_widget
({
int
selectedIndex
=
0
})
{
return
_buildWidget
(
NavigationBar
(
selectedIndex:
selectedIndex
,
destinations:
const
<
Widget
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
ac_unit
),
label:
'AC'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
access_alarm
),
label:
'Alarm'
,
),
],
),
);
}
await
tester
.
pumpWidget
(
_widget
(
selectedIndex:
0
));
expect
(
tester
.
getSemantics
(
find
.
text
(
'AC'
)),
matchesSemantics
(
label:
'AC
\n
Tab 1 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
true
,
hasTapAction:
true
,
),
);
expect
(
tester
.
getSemantics
(
find
.
text
(
'Alarm'
)),
matchesSemantics
(
label:
'Alarm
\n
Tab 2 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
false
,
hasTapAction:
true
,
),
);
await
tester
.
pumpWidget
(
_widget
(
selectedIndex:
1
));
expect
(
tester
.
getSemantics
(
find
.
text
(
'AC'
)),
matchesSemantics
(
label:
'AC
\n
Tab 1 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
false
,
hasTapAction:
true
,
),
);
expect
(
tester
.
getSemantics
(
find
.
text
(
'Alarm'
)),
matchesSemantics
(
label:
'Alarm
\n
Tab 2 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
true
,
hasTapAction:
true
,
),
);
});
testWidgets
(
'Navigation bar semantics with some labels hidden'
,
(
WidgetTester
tester
)
async
{
Widget
_widget
({
int
selectedIndex
=
0
})
{
return
_buildWidget
(
NavigationBar
(
labelBehavior:
NavigationDestinationLabelBehavior
.
onlyShowSelected
,
selectedIndex:
selectedIndex
,
destinations:
const
<
Widget
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
ac_unit
),
label:
'AC'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
access_alarm
),
label:
'Alarm'
,
),
],
),
);
}
await
tester
.
pumpWidget
(
_widget
(
selectedIndex:
0
));
expect
(
tester
.
getSemantics
(
find
.
text
(
'AC'
)),
matchesSemantics
(
label:
'AC
\n
Tab 1 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
true
,
hasTapAction:
true
,
),
);
expect
(
tester
.
getSemantics
(
find
.
text
(
'Alarm'
)),
matchesSemantics
(
label:
'Alarm
\n
Tab 2 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
false
,
hasTapAction:
true
,
),
);
await
tester
.
pumpWidget
(
_widget
(
selectedIndex:
1
));
expect
(
tester
.
getSemantics
(
find
.
text
(
'AC'
)),
matchesSemantics
(
label:
'AC
\n
Tab 1 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
false
,
hasTapAction:
true
,
),
);
expect
(
tester
.
getSemantics
(
find
.
text
(
'Alarm'
)),
matchesSemantics
(
label:
'Alarm
\n
Tab 2 of 2'
,
textDirection:
TextDirection
.
ltr
,
isFocusable:
true
,
isSelected:
true
,
hasTapAction:
true
,
),
);
});
testWidgets
(
'Navigation bar does not grow with text scale factor'
,
(
WidgetTester
tester
)
async
{
const
int
_animationMilliseconds
=
800
;
Widget
_widget
({
double
textScaleFactor
=
1
})
{
return
_buildWidget
(
MediaQuery
(
data:
MediaQueryData
(
textScaleFactor:
textScaleFactor
),
child:
NavigationBar
(
animationDuration:
const
Duration
(
milliseconds:
_animationMilliseconds
),
destinations:
const
<
NavigationDestination
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
ac_unit
),
label:
'AC'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
access_alarm
),
label:
'Alarm'
,
),
],
),
),
);
}
await
tester
.
pumpWidget
(
_widget
());
final
double
initialHeight
=
tester
.
getSize
(
find
.
byType
(
NavigationBar
)).
height
;
await
tester
.
pumpWidget
(
_widget
(
textScaleFactor:
2
));
final
double
newHeight
=
tester
.
getSize
(
find
.
byType
(
NavigationBar
)).
height
;
expect
(
newHeight
,
equals
(
initialHeight
));
});
}
Widget
_buildWidget
(
Widget
child
)
{
return
MaterialApp
(
theme:
ThemeData
.
light
(),
home:
Scaffold
(
bottomNavigationBar:
Center
(
child:
child
,
),
),
);
}
Material
_getMaterial
(
WidgetTester
tester
)
{
return
tester
.
firstWidget
<
Material
>(
find
.
descendant
(
of:
find
.
byType
(
NavigationBar
),
matching:
find
.
byType
(
Material
)),
);
}
packages/flutter/test/material/navigation_bar_theme_test.dart
0 → 100644
View file @
f3049c77
// 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'
;
void
main
(
)
{
test
(
'copyWith, ==, hashCode basics'
,
()
{
expect
(
const
NavigationBarThemeData
(),
const
NavigationBarThemeData
().
copyWith
());
expect
(
const
NavigationBarThemeData
().
hashCode
,
const
NavigationBarThemeData
().
copyWith
().
hashCode
);
});
testWidgets
(
'Default debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
NavigationBarThemeData
().
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
(
'Custom debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
NavigationBarThemeData
(
height:
200.0
,
backgroundColor:
const
Color
(
0x00000099
),
indicatorColor:
const
Color
(
0x00000098
),
labelTextStyle:
MaterialStateProperty
.
all
(
const
TextStyle
(
fontSize:
7.0
)),
iconTheme:
MaterialStateProperty
.
all
(
const
IconThemeData
(
color:
Color
(
0x00000097
))),
labelBehavior:
NavigationDestinationLabelBehavior
.
alwaysHide
,
).
debugFillProperties
(
builder
);
final
List
<
String
>
description
=
builder
.
properties
.
where
((
DiagnosticsNode
node
)
=>
!
node
.
isFiltered
(
DiagnosticLevel
.
info
))
.
map
((
DiagnosticsNode
node
)
=>
node
.
toString
())
.
toList
();
expect
(
description
[
0
],
'height: 200.0'
);
expect
(
description
[
1
],
'backgroundColor: Color(0x00000099)'
);
expect
(
description
[
2
],
'indicatorColor: Color(0x00000098)'
);
expect
(
description
[
3
],
'labelTextStyle: MaterialStateProperty.all(TextStyle(inherit: true, size: 7.0))'
);
// Ignore instance address for IconThemeData.
expect
(
description
[
4
].
contains
(
'iconTheme: MaterialStateProperty.all(IconThemeData'
),
isTrue
);
expect
(
description
[
4
].
contains
(
'(color: Color(0x00000097))'
),
isTrue
);
expect
(
description
[
5
],
'labelBehavior: NavigationDestinationLabelBehavior.alwaysHide'
);
});
testWidgets
(
'NavigationBarThemeData values are used when no NavigationBar properties are specified'
,
(
WidgetTester
tester
)
async
{
const
double
height
=
200.0
;
const
Color
backgroundColor
=
Color
(
0x00000001
);
const
Color
indicatorColor
=
Color
(
0x00000002
);
const
double
selectedIconSize
=
25.0
;
const
double
unselectedIconSize
=
23.0
;
const
Color
selectedIconColor
=
Color
(
0x00000003
);
const
Color
unselectedIconColor
=
Color
(
0x00000004
);
const
double
selectedIconOpacity
=
0.99
;
const
double
unselectedIconOpacity
=
0.98
;
const
double
selectedLabelFontSize
=
13.0
;
const
double
unselectedLabelFontSize
=
11.0
;
const
NavigationDestinationLabelBehavior
labelBehavior
=
NavigationDestinationLabelBehavior
.
alwaysShow
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
bottomNavigationBar:
NavigationBarTheme
(
data:
NavigationBarThemeData
(
height:
height
,
backgroundColor:
backgroundColor
,
indicatorColor:
indicatorColor
,
iconTheme:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
const
IconThemeData
(
size:
selectedIconSize
,
color:
selectedIconColor
,
opacity:
selectedIconOpacity
,
);
}
return
const
IconThemeData
(
size:
unselectedIconSize
,
color:
unselectedIconColor
,
opacity:
unselectedIconOpacity
,
);
}),
labelTextStyle:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
const
TextStyle
(
fontSize:
selectedLabelFontSize
);
}
return
const
TextStyle
(
fontSize:
unselectedLabelFontSize
);
}),
labelBehavior:
labelBehavior
,
),
child:
NavigationBar
(
selectedIndex:
0
,
destinations:
_destinations
(),
),
),
),
),
);
expect
(
_barHeight
(
tester
),
height
);
expect
(
_barMaterial
(
tester
).
color
,
backgroundColor
);
expect
(
_indicator
(
tester
)?.
color
,
indicatorColor
);
expect
(
_selectedIconTheme
(
tester
).
size
,
selectedIconSize
);
expect
(
_selectedIconTheme
(
tester
).
color
,
selectedIconColor
);
expect
(
_selectedIconTheme
(
tester
).
opacity
,
selectedIconOpacity
);
expect
(
_unselectedIconTheme
(
tester
).
size
,
unselectedIconSize
);
expect
(
_unselectedIconTheme
(
tester
).
color
,
unselectedIconColor
);
expect
(
_unselectedIconTheme
(
tester
).
opacity
,
unselectedIconOpacity
);
expect
(
_selectedLabelStyle
(
tester
).
fontSize
,
selectedLabelFontSize
);
expect
(
_unselectedLabelStyle
(
tester
).
fontSize
,
unselectedLabelFontSize
);
expect
(
_labelBehavior
(
tester
),
labelBehavior
);
});
testWidgets
(
'NavigationBar values take priority over NavigationBarThemeData values when both properties are specified'
,
(
WidgetTester
tester
)
async
{
const
double
height
=
200.0
;
const
Color
backgroundColor
=
Color
(
0x00000001
);
const
NavigationDestinationLabelBehavior
labelBehavior
=
NavigationDestinationLabelBehavior
.
alwaysShow
;
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Scaffold
(
bottomNavigationBar:
NavigationBarTheme
(
data:
const
NavigationBarThemeData
(
height:
100.0
,
backgroundColor:
Color
(
0x00000099
),
labelBehavior:
NavigationDestinationLabelBehavior
.
alwaysHide
,
),
child:
NavigationBar
(
height:
height
,
backgroundColor:
backgroundColor
,
labelBehavior:
labelBehavior
,
selectedIndex:
0
,
destinations:
_destinations
(),
),
),
),
),
);
expect
(
_barHeight
(
tester
),
height
);
expect
(
_barMaterial
(
tester
).
color
,
backgroundColor
);
expect
(
_labelBehavior
(
tester
),
labelBehavior
);
});
}
List
<
NavigationDestination
>
_destinations
()
{
return
const
<
NavigationDestination
>[
NavigationDestination
(
icon:
Icon
(
Icons
.
favorite_border
),
selectedIcon:
Icon
(
Icons
.
favorite
),
label:
'Abc'
,
),
NavigationDestination
(
icon:
Icon
(
Icons
.
star_border
),
selectedIcon:
Icon
(
Icons
.
star
),
label:
'Def'
,
),
];
}
double
_barHeight
(
WidgetTester
tester
)
{
return
tester
.
getRect
(
find
.
byType
(
NavigationBar
),
).
height
;
}
Material
_barMaterial
(
WidgetTester
tester
)
{
return
tester
.
firstWidget
<
Material
>(
find
.
descendant
(
of:
find
.
byType
(
NavigationBar
),
matching:
find
.
byType
(
Material
),
),
);
}
BoxDecoration
?
_indicator
(
WidgetTester
tester
)
{
return
tester
.
firstWidget
<
Container
>(
find
.
descendant
(
of:
find
.
byType
(
FadeTransition
),
matching:
find
.
byType
(
Container
),
),
).
decoration
as
BoxDecoration
?;
}
IconThemeData
_selectedIconTheme
(
WidgetTester
tester
)
{
return
_iconTheme
(
tester
,
Icons
.
favorite
);
}
IconThemeData
_unselectedIconTheme
(
WidgetTester
tester
)
{
return
_iconTheme
(
tester
,
Icons
.
star_border
);
}
IconThemeData
_iconTheme
(
WidgetTester
tester
,
IconData
icon
)
{
return
tester
.
firstWidget
<
IconTheme
>(
find
.
ancestor
(
of:
find
.
byIcon
(
icon
),
matching:
find
.
byType
(
IconTheme
),
),
).
data
;
}
TextStyle
_selectedLabelStyle
(
WidgetTester
tester
)
{
return
tester
.
widget
<
RichText
>(
find
.
descendant
(
of:
find
.
text
(
'Abc'
),
matching:
find
.
byType
(
RichText
),
),
).
text
.
style
!;
}
TextStyle
_unselectedLabelStyle
(
WidgetTester
tester
)
{
return
tester
.
widget
<
RichText
>(
find
.
descendant
(
of:
find
.
text
(
'Def'
),
matching:
find
.
byType
(
RichText
),
),
).
text
.
style
!;
}
NavigationDestinationLabelBehavior
_labelBehavior
(
WidgetTester
tester
)
{
if
(
_opacityAboveLabel
(
'Abc'
).
evaluate
().
isNotEmpty
&&
_opacityAboveLabel
(
'Def'
).
evaluate
().
isNotEmpty
)
{
return
_labelOpacity
(
tester
,
'Abc'
)
==
1
?
NavigationDestinationLabelBehavior
.
onlyShowSelected
:
NavigationDestinationLabelBehavior
.
alwaysHide
;
}
else
{
return
NavigationDestinationLabelBehavior
.
alwaysShow
;
}
}
Finder
_opacityAboveLabel
(
String
text
)
{
return
find
.
ancestor
(
of:
find
.
text
(
text
),
matching:
find
.
byType
(
Opacity
),
);
}
// Only valid when labelBehavior != alwaysShow.
double
_labelOpacity
(
WidgetTester
tester
,
String
text
)
{
final
Opacity
opacityWidget
=
tester
.
widget
<
Opacity
>(
find
.
ancestor
(
of:
find
.
text
(
text
),
matching:
find
.
byType
(
Opacity
),
),
);
return
opacityWidget
.
opacity
;
}
packages/flutter/test/material/theme_data_test.dart
View file @
f3049c77
...
@@ -324,6 +324,7 @@ void main() {
...
@@ -324,6 +324,7 @@ void main() {
colorScheme:
const
ColorScheme
.
light
(),
colorScheme:
const
ColorScheme
.
light
(),
dialogTheme:
const
DialogTheme
(
backgroundColor:
Colors
.
black
),
dialogTheme:
const
DialogTheme
(
backgroundColor:
Colors
.
black
),
floatingActionButtonTheme:
const
FloatingActionButtonThemeData
(
backgroundColor:
Colors
.
black
),
floatingActionButtonTheme:
const
FloatingActionButtonThemeData
(
backgroundColor:
Colors
.
black
),
navigationBarTheme:
const
NavigationBarThemeData
(
backgroundColor:
Colors
.
black
),
navigationRailTheme:
const
NavigationRailThemeData
(
backgroundColor:
Colors
.
black
),
navigationRailTheme:
const
NavigationRailThemeData
(
backgroundColor:
Colors
.
black
),
typography:
Typography
.
material2018
(
platform:
TargetPlatform
.
android
),
typography:
Typography
.
material2018
(
platform:
TargetPlatform
.
android
),
cupertinoOverrideTheme:
null
,
cupertinoOverrideTheme:
null
,
...
@@ -420,6 +421,7 @@ void main() {
...
@@ -420,6 +421,7 @@ void main() {
colorScheme:
const
ColorScheme
.
light
(),
colorScheme:
const
ColorScheme
.
light
(),
dialogTheme:
const
DialogTheme
(
backgroundColor:
Colors
.
white
),
dialogTheme:
const
DialogTheme
(
backgroundColor:
Colors
.
white
),
floatingActionButtonTheme:
const
FloatingActionButtonThemeData
(
backgroundColor:
Colors
.
white
),
floatingActionButtonTheme:
const
FloatingActionButtonThemeData
(
backgroundColor:
Colors
.
white
),
navigationBarTheme:
const
NavigationBarThemeData
(
backgroundColor:
Colors
.
white
),
navigationRailTheme:
const
NavigationRailThemeData
(
backgroundColor:
Colors
.
white
),
navigationRailTheme:
const
NavigationRailThemeData
(
backgroundColor:
Colors
.
white
),
typography:
Typography
.
material2018
(
platform:
TargetPlatform
.
iOS
),
typography:
Typography
.
material2018
(
platform:
TargetPlatform
.
iOS
),
cupertinoOverrideTheme:
ThemeData
.
light
().
cupertinoOverrideTheme
,
cupertinoOverrideTheme:
ThemeData
.
light
().
cupertinoOverrideTheme
,
...
@@ -497,6 +499,7 @@ void main() {
...
@@ -497,6 +499,7 @@ void main() {
colorScheme:
otherTheme
.
colorScheme
,
colorScheme:
otherTheme
.
colorScheme
,
dialogTheme:
otherTheme
.
dialogTheme
,
dialogTheme:
otherTheme
.
dialogTheme
,
floatingActionButtonTheme:
otherTheme
.
floatingActionButtonTheme
,
floatingActionButtonTheme:
otherTheme
.
floatingActionButtonTheme
,
navigationBarTheme:
otherTheme
.
navigationBarTheme
,
navigationRailTheme:
otherTheme
.
navigationRailTheme
,
navigationRailTheme:
otherTheme
.
navigationRailTheme
,
typography:
otherTheme
.
typography
,
typography:
otherTheme
.
typography
,
cupertinoOverrideTheme:
otherTheme
.
cupertinoOverrideTheme
,
cupertinoOverrideTheme:
otherTheme
.
cupertinoOverrideTheme
,
...
@@ -571,6 +574,7 @@ void main() {
...
@@ -571,6 +574,7 @@ void main() {
expect
(
themeDataCopy
.
colorScheme
,
equals
(
otherTheme
.
colorScheme
));
expect
(
themeDataCopy
.
colorScheme
,
equals
(
otherTheme
.
colorScheme
));
expect
(
themeDataCopy
.
dialogTheme
,
equals
(
otherTheme
.
dialogTheme
));
expect
(
themeDataCopy
.
dialogTheme
,
equals
(
otherTheme
.
dialogTheme
));
expect
(
themeDataCopy
.
floatingActionButtonTheme
,
equals
(
otherTheme
.
floatingActionButtonTheme
));
expect
(
themeDataCopy
.
floatingActionButtonTheme
,
equals
(
otherTheme
.
floatingActionButtonTheme
));
expect
(
themeDataCopy
.
navigationBarTheme
,
equals
(
otherTheme
.
navigationBarTheme
));
expect
(
themeDataCopy
.
navigationRailTheme
,
equals
(
otherTheme
.
navigationRailTheme
));
expect
(
themeDataCopy
.
navigationRailTheme
,
equals
(
otherTheme
.
navigationRailTheme
));
expect
(
themeDataCopy
.
typography
,
equals
(
otherTheme
.
typography
));
expect
(
themeDataCopy
.
typography
,
equals
(
otherTheme
.
typography
));
expect
(
themeDataCopy
.
cupertinoOverrideTheme
,
equals
(
otherTheme
.
cupertinoOverrideTheme
));
expect
(
themeDataCopy
.
cupertinoOverrideTheme
,
equals
(
otherTheme
.
cupertinoOverrideTheme
));
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment