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
6ec2bd0a
Unverified
Commit
6ec2bd0a
authored
Nov 12, 2022
by
Darren Austin
Committed by
GitHub
Nov 12, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
M3 Segmented Button widget (#113723)
parent
87727681
Changes
10
Show whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
2172 additions
and
7 deletions
+2172
-7
gen_defaults.dart
dev/tools/gen_defaults/bin/gen_defaults.dart
+2
-0
segmented_button_template.dart
dev/tools/gen_defaults/lib/segmented_button_template.dart
+124
-0
segmented_button.0.dart
...api/lib/material/segmented_button/segmented_button.0.dart
+106
-0
material.dart
packages/flutter/lib/material.dart
+2
-0
segmented_button.dart
packages/flutter/lib/src/material/segmented_button.dart
+813
-0
segmented_button_theme.dart
...ages/flutter/lib/src/material/segmented_button_theme.dart
+172
-0
theme_data.dart
packages/flutter/lib/src/material/theme_data.dart
+21
-7
segmented_button_test.dart
packages/flutter/test/material/segmented_button_test.dart
+456
-0
segmented_button_theme_test.dart
...es/flutter/test/material/segmented_button_theme_test.dart
+473
-0
theme_data_test.dart
packages/flutter/test/material/theme_data_test.dart
+3
-0
No files found.
dev/tools/gen_defaults/bin/gen_defaults.dart
View file @
6ec2bd0a
...
...
@@ -40,6 +40,7 @@ import 'package:gen_defaults/navigation_rail_template.dart';
import
'package:gen_defaults/popup_menu_template.dart'
;
import
'package:gen_defaults/progress_indicator_template.dart'
;
import
'package:gen_defaults/radio_template.dart'
;
import
'package:gen_defaults/segmented_button_template.dart'
;
import
'package:gen_defaults/slider_template.dart'
;
import
'package:gen_defaults/surface_tint.dart'
;
import
'package:gen_defaults/switch_template.dart'
;
...
...
@@ -155,6 +156,7 @@ Future<void> main(List<String> args) async {
PopupMenuTemplate
(
'PopupMenu'
,
'
$materialLib
/popup_menu.dart'
,
tokens
).
updateFile
();
ProgressIndicatorTemplate
(
'ProgressIndicator'
,
'
$materialLib
/progress_indicator.dart'
,
tokens
).
updateFile
();
RadioTemplate
(
'Radio<T>'
,
'
$materialLib
/radio.dart'
,
tokens
).
updateFile
();
SegmentedButtonTemplate
(
'SegmentedButton'
,
'
$materialLib
/segmented_button.dart'
,
tokens
).
updateFile
();
SliderTemplate
(
'md.comp.slider'
,
'Slider'
,
'
$materialLib
/slider.dart'
,
tokens
).
updateFile
();
SurfaceTintTemplate
(
'SurfaceTint'
,
'
$materialLib
/elevation_overlay.dart'
,
tokens
).
updateFile
();
SwitchTemplate
(
'Switch'
,
'
$materialLib
/switch.dart'
,
tokens
).
updateFile
();
...
...
dev/tools/gen_defaults/lib/segmented_button_template.dart
0 → 100644
View file @
6ec2bd0a
// 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
'template.dart'
;
class
SegmentedButtonTemplate
extends
TokenTemplate
{
const
SegmentedButtonTemplate
(
super
.
blockName
,
super
.
fileName
,
super
.
tokens
,
{
super
.
colorSchemePrefix
=
'_colors.'
,
});
String
_layerOpacity
(
String
layerToken
)
{
if
(
tokens
.
containsKey
(
layerToken
))
{
final
String
?
layerValue
=
tokens
[
layerToken
]
as
String
?;
if
(
tokens
.
containsKey
(
layerValue
))
{
final
String
?
opacityValue
=
opacity
(
layerValue
!);
if
(
opacityValue
!=
null
)
{
return
'.withOpacity(
$opacityValue
)'
;
}
}
}
return
''
;
}
String
_stateColor
(
String
componentToken
,
String
type
,
String
state
)
{
final
String
baseColor
=
color
(
'
$componentToken
.
$type
.
$state
.state-layer.color'
,
''
);
if
(
baseColor
.
isEmpty
)
{
return
'null'
;
}
final
String
opacity
=
_layerOpacity
(
'
$componentToken
.
$state
.state-layer.opacity'
);
return
'
$baseColor$opacity
'
;
}
@override
String
generate
()
=>
'''
class _SegmentedButtonDefaultsM3 extends SegmentedButtonThemeData {
_SegmentedButtonDefaultsM3(this.context);
final BuildContext context;
late final ThemeData _theme = Theme.of(context);
late final ColorScheme _colors = _theme.colorScheme;
@override ButtonStyle? get style {
return ButtonStyle(
textStyle: MaterialStatePropertyAll<TextStyle?>(
${textStyle('md.comp.outlined-segmented-button.label-text')}
),
backgroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return
${componentColor('md.comp.outlined-segmented-button.disabled')}
;
}
if (states.contains(MaterialState.selected)) {
return
${componentColor('md.comp.outlined-segmented-button.selected.container')}
;
}
return
${componentColor('md.comp.outlined-segmented-button.unselected.container')}
;
}),
foregroundColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return
${componentColor('md.comp.outlined-segmented-button.disabled.label-text')}
;
}
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.pressed)) {
return
${componentColor('md.comp.outlined-segmented-button.selected.pressed.label-text')}
;
}
if (states.contains(MaterialState.hovered)) {
return
${componentColor('md.comp.outlined-segmented-button.selected.hover.label-text')}
;
}
if (states.contains(MaterialState.focused)) {
return
${componentColor('md.comp.outlined-segmented-button.selected.focus.label-text')}
;
}
return
${componentColor('md.comp.outlined-segmented-button.selected.label-text')}
;
} else {
if (states.contains(MaterialState.pressed)) {
return
${componentColor('md.comp.outlined-segmented-button.unselected.pressed.label-text')}
;
}
if (states.contains(MaterialState.hovered)) {
return
${componentColor('md.comp.outlined-segmented-button.unselected.hover.label-text')}
;
}
if (states.contains(MaterialState.focused)) {
return
${componentColor('md.comp.outlined-segmented-button.unselected.focus.label-text')}
;
}
return
${componentColor('md.comp.outlined-segmented-button.unselected.container')}
;
}
}),
overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
if (states.contains(MaterialState.hovered)) {
return
${_stateColor('md.comp.outlined-segmented-button', 'selected', 'hover')}
;
}
if (states.contains(MaterialState.focused)) {
return
${_stateColor('md.comp.outlined-segmented-button', 'selected', 'focus')}
;
}
if (states.contains(MaterialState.pressed)) {
return
${_stateColor('md.comp.outlined-segmented-button', 'selected', 'pressed')}
;
}
} else {
if (states.contains(MaterialState.hovered)) {
return
${_stateColor('md.comp.outlined-segmented-button', 'unselected', 'hover')}
;
}
if (states.contains(MaterialState.focused)) {
return
${_stateColor('md.comp.outlined-segmented-button', 'unselected', 'focus')}
;
}
if (states.contains(MaterialState.pressed)) {
return
${_stateColor('md.comp.outlined-segmented-button', 'unselected', 'pressed')}
;
}
}
return null;
}),
surfaceTintColor: const MaterialStatePropertyAll<Color>(Colors.transparent),
elevation: const MaterialStatePropertyAll<double>(0),
iconSize: const MaterialStatePropertyAll<double?>(
${tokens['md.comp.outlined-segmented-button.with-icon.icon.size']}
),
side: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return
${border("md.comp.outlined-segmented-button.disabled.outline")}
;
}
return
${border("md.comp.outlined-segmented-button.outline")}
;
}),
shape: const MaterialStatePropertyAll<OutlinedBorder>(
${shape("md.comp.outlined-segmented-button", '')}
),
);
}
@override
Widget? get selectedIcon => const Icon(Icons.check);
}
'''
;
}
examples/api/lib/material/segmented_button/segmented_button.0.dart
0 → 100644
View file @
6ec2bd0a
// 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.
/// Flutter code sample for [SegmentedButton].
import
'package:flutter/material.dart'
;
void
main
(
)
{
runApp
(
const
SegmentedButtonApp
());
}
class
SegmentedButtonApp
extends
StatelessWidget
{
const
SegmentedButtonApp
({
super
.
key
});
@override
Widget
build
(
BuildContext
context
)
{
return
MaterialApp
(
theme:
ThemeData
(
useMaterial3:
true
),
home:
Scaffold
(
body:
Center
(
child:
Column
(
mainAxisAlignment:
MainAxisAlignment
.
center
,
children:
const
<
Widget
>[
Spacer
(),
Text
(
'Single choice'
),
SingleChoice
(),
SizedBox
(
height:
20
),
Text
(
'Multiple choice'
),
MultipleChoice
(),
Spacer
(),
],
),
),
),
);
}
}
enum
Calendar
{
day
,
week
,
month
,
year
}
class
SingleChoice
extends
StatefulWidget
{
const
SingleChoice
({
super
.
key
});
@override
State
<
SingleChoice
>
createState
()
=>
_SingleChoiceState
();
}
class
_SingleChoiceState
extends
State
<
SingleChoice
>
{
Calendar
calendarView
=
Calendar
.
day
;
@override
Widget
build
(
BuildContext
context
)
{
return
SegmentedButton
<
Calendar
>(
segments:
const
<
ButtonSegment
<
Calendar
>>[
ButtonSegment
<
Calendar
>(
value:
Calendar
.
day
,
label:
Text
(
'Day'
),
icon:
Icon
(
Icons
.
calendar_view_day
)),
ButtonSegment
<
Calendar
>(
value:
Calendar
.
week
,
label:
Text
(
'Week'
),
icon:
Icon
(
Icons
.
calendar_view_week
)),
ButtonSegment
<
Calendar
>(
value:
Calendar
.
month
,
label:
Text
(
'Month'
),
icon:
Icon
(
Icons
.
calendar_view_month
)),
ButtonSegment
<
Calendar
>(
value:
Calendar
.
year
,
label:
Text
(
'Year'
),
icon:
Icon
(
Icons
.
calendar_today
)),
],
selected:
<
Calendar
>{
calendarView
},
onSelectionChanged:
(
Set
<
Calendar
>
newSelection
)
{
setState
(()
{
// By default there is only a single segment that can be
// selected at one time, so its value is always the first
// item in the selected set.
calendarView
=
newSelection
.
first
;
});
},
);
}
}
enum
Sizes
{
extraSmall
,
small
,
medium
,
large
,
extraLarge
}
class
MultipleChoice
extends
StatefulWidget
{
const
MultipleChoice
({
super
.
key
});
@override
State
<
MultipleChoice
>
createState
()
=>
_MultipleChoiceState
();
}
class
_MultipleChoiceState
extends
State
<
MultipleChoice
>
{
Set
<
Sizes
>
selection
=
<
Sizes
>{
Sizes
.
large
,
Sizes
.
extraLarge
};
@override
Widget
build
(
BuildContext
context
)
{
return
SegmentedButton
<
Sizes
>(
segments:
const
<
ButtonSegment
<
Sizes
>>[
ButtonSegment
<
Sizes
>(
value:
Sizes
.
extraSmall
,
label:
Text
(
'XS'
)),
ButtonSegment
<
Sizes
>(
value:
Sizes
.
small
,
label:
Text
(
'S'
)),
ButtonSegment
<
Sizes
>(
value:
Sizes
.
medium
,
label:
Text
(
'M'
)),
ButtonSegment
<
Sizes
>(
value:
Sizes
.
large
,
label:
Text
(
'L'
),),
ButtonSegment
<
Sizes
>(
value:
Sizes
.
extraLarge
,
label:
Text
(
'XL'
)),
],
selected:
selection
,
onSelectionChanged:
(
Set
<
Sizes
>
newSelection
)
{
setState
(()
{
selection
=
newSelection
;
});
},
multiSelectionEnabled:
true
,
);
}
}
packages/flutter/lib/material.dart
View file @
6ec2bd0a
...
...
@@ -146,6 +146,8 @@ export 'src/material/scaffold.dart';
export
'src/material/scrollbar.dart'
;
export
'src/material/scrollbar_theme.dart'
;
export
'src/material/search.dart'
;
export
'src/material/segmented_button.dart'
;
export
'src/material/segmented_button_theme.dart'
;
export
'src/material/selectable_text.dart'
;
export
'src/material/selection_area.dart'
;
export
'src/material/shadows.dart'
;
...
...
packages/flutter/lib/src/material/segmented_button.dart
0 → 100644
View file @
6ec2bd0a
// 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:math'
as
math
;
import
'package:flutter/foundation.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'color_scheme.dart'
;
import
'colors.dart'
;
import
'icons.dart'
;
import
'material.dart'
;
import
'material_state.dart'
;
import
'segmented_button_theme.dart'
;
import
'text_button.dart'
;
import
'text_button_theme.dart'
;
import
'theme.dart'
;
/// Data describing a segment of a [SegmentedButton].
class
ButtonSegment
<
T
>
{
/// Construct a SegmentData
///
/// One of [icon] or [label] must be non-null.
const
ButtonSegment
({
required
this
.
value
,
this
.
icon
,
this
.
label
,
this
.
enabled
=
true
,
})
:
assert
(
icon
!=
null
||
label
!=
null
);
/// Value used to identify the segment.
///
/// This value must be unique across all segments in a [SegmentedButton].
final
T
value
;
/// Optional icon displayed in the segment.
final
Widget
?
icon
;
/// Optional label displayed in the segment.
final
Widget
?
label
;
/// Determines if the segment is available for selection.
final
bool
enabled
;
}
/// A Material button that allows the user to select from limited set of options.
///
/// Segmented buttons are used to help people select options, switch views, or
/// sort elements. They are typically used in cases where there are only 2-5
/// options.
///
/// The options are represented by segments described with [ButtonSegment]
/// entries in the [segments] field. Each segment has a [ButtonSegment.value]
/// that is used to indicate which segments are selected.
///
/// The [selected] field is a set of selected [ButtonSegment.value]s. This
/// should be updated by the app in response to [onSelectionChanged] updates.
///
/// By default, only a single segment can be selected (for mutually exclusive
/// choices). This can be relaxed with the [multiSelectionEnabled] field.
///
/// Like [ButtonStyleButton]s, the [SegmentedButton]'s visuals can be
/// configured with a [ButtonStyle] [style] field. However, unlike other
/// buttons, some of the style parameters are applied to the entire segmented
/// button, and others are used for each of the segments.
///
/// By default, a checkmark icon is used to show selected items. To configure
/// this behavior, you can use the [showSelectedIcon] and [selectedIcon] fields.
///
/// Individual segments can be enabled or disabled with their
/// [ButtonSegment.enabled] flag. If the [onSelectionChanged] field is null,
/// then the entire segmented button will be disabled, regardless of the
/// individual segment settings.
///
/// {@tool dartpad}
/// This sample shows how to display a [SegmentedButton] with either a single or
/// multiple selection.
///
/// ** See code in examples/api/lib/material/segmented_button/segmented_button.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * Material Design spec: <https://m3.material.io/components/segmented-buttons/overview>
/// * [ButtonStyle], which can be used in the [style] field to configure
/// the appearance of the button and its segments.
/// * [ToggleButtons], a similar widget that was built for Material 2.
/// [SegmentedButton] should be considered as a replacement for
/// [ToggleButtons].
/// * [Radio], an alternative way to present the user with a mutually exclusive set of options.
/// * [FilterChip], [ChoiceChip], which can be used when you need to show more than five options.
class
SegmentedButton
<
T
>
extends
StatelessWidget
{
/// Creates a const [SegmentedButton].
///
/// [segments] must contain at least one segment, but it is recommended
/// to have two to five segments. If you need only single segment,
/// consider using a [Checkbox] or [Radio] widget instead. If you need
/// more than five options, consider using [FilterChip] or [ChoiceChip]
/// widgets.
///
/// If [onSelectionChanged] is null, then the entire segemented button will
/// be disabled.
///
/// By default [selected] must only contain one entry. However, if
/// [multiSelectionEnabled] is true, then [selected] can contain multiple
/// entries. If [emptySelectionAllowed] is true, then [selected] can be empty.
const
SegmentedButton
({
super
.
key
,
required
this
.
segments
,
required
this
.
selected
,
this
.
onSelectionChanged
,
this
.
multiSelectionEnabled
=
false
,
this
.
emptySelectionAllowed
=
false
,
this
.
style
,
this
.
showSelectedIcon
=
true
,
this
.
selectedIcon
,
})
:
assert
(
segments
!=
null
),
assert
(
segments
.
length
>
0
),
assert
(
selected
!=
null
),
assert
(
selected
.
length
>
0
||
emptySelectionAllowed
),
assert
(
selected
.
length
<
2
||
multiSelectionEnabled
);
/// Descriptions of the segments in the button.
///
/// This a required parameter and must contain at least one segment,
/// but it is recommended to contain two to five segments. If you need only
/// a single segment, consider using a [Checkbox] or [Radio] widget instead.
/// If you need more than five options, consider using [FilterChip] or
/// [ChoiceChip] widgets.
final
List
<
ButtonSegment
<
T
>>
segments
;
/// The set of [ButtonSegment.value]s that indicate which [segments] are
/// selected.
///
/// As the [SegmentedButton] does not maintain the state of the selection,
/// you will need to update this in response to [onSelectionChanged] calls.
///
/// This is a required parameter.
final
Set
<
T
>
selected
;
/// The function that is called when the selection changes.
///
/// The callback's parameter indicates which of the segments are selected.
///
/// When the callback is null, the entire [SegmentedButton] is disabled,
/// and will not respond to input.
///
/// The default is null.
final
void
Function
(
Set
<
T
>)?
onSelectionChanged
;
/// Determines if multiple segments can be selected at one time.
///
/// If true, more than one segment can be selected. When selecting a
/// segment, the other selected segments will stay selected. Selecting an
/// already selected segment will unselect it.
///
/// If false, only one segment may be selected at a time. When a segment
/// is selected, any previously selected segment will be unselected.
///
/// The default is false, so only a single segement may be selected at one
/// time.
final
bool
multiSelectionEnabled
;
/// Determines if having no selected segments is allowed.
///
/// If true, then it is acceptable for none of the segements to be selected.
/// This means that [selected] can be empty. If the user taps on a
/// selected segment, it will be removed from the selection set passed into
/// [onSelectionChanged].
///
/// If false (the default), there must be at least one segment selected. If
/// the user taps on the only selected segment it will not be deselected, and
/// [onSelectionChanged] will not be called.
final
bool
emptySelectionAllowed
;
/// Customizes this button's appearance.
///
/// The following style properties apply to the entire segmented button:
///
/// * [ButtonStyle.shadowColor]
/// * [ButtonStyle.elevation]
/// * [ButtonStyle.side] - which is used for both the outer shape and
/// dividers between segments.
/// * [ButtonStyle.shape]
///
/// The following style properties are applied to each of the invidual
/// button segments. For properties that are a [MaterialStateProperty],
/// they will be resolved with the current state of the segment:
///
/// * [ButtonStyle.textStyle]
/// * [ButtonStyle.backgroundColor]
/// * [ButtonStyle.foregroundColor]
/// * [ButtonStyle.overlayColor]
/// * [ButtonStyle.surfaceTintColor]
/// * [ButtonStyle.elevation]
/// * [ButtonStyle.padding]
/// * [ButtonStyle.iconColor]
/// * [ButtonStyle.iconSize]
/// * [ButtonStyle.mouseCursor]
/// * [ButtonStyle.visualDensity]
/// * [ButtonStyle.tapTargetSize]
/// * [ButtonStyle.animationDuration]
/// * [ButtonStyle.enableFeedback]
/// * [ButtonStyle.alignment]
/// * [ButtonStyle.splashFactory]
final
ButtonStyle
?
style
;
/// Determines if the [selectedIcon] (usually an icon using [Icons.check])
/// is displayed on the selected segments.
///
/// If true, the [selectedIcon] will be displayed at the start of the segment.
/// If both the [ButtonSegment.label] and [ButtonSegment.icon] are provided,
/// then the icon will be replaced with the [selectedIcon]. If only the icon
/// or the label is present then the [selectedIcon] will be shown at the start
/// of the segment.
///
/// If false, then the [selectedIcon] is not used and will not be displayed
/// on selected segments.
///
/// The default is true, meaning the [selectedIcon] will be shown on selected
/// segments.
final
bool
showSelectedIcon
;
/// An icon that is used to indicate a segment is selected.
///
/// If [showSelectedIcon] is true then for selected segments this icon
/// will be shown before the [ButtonSegment.label], replacing the
/// [ButtonSegment.icon] if it is specified.
///
/// Defaults to an [Icon] with [Icons.check].
final
Widget
?
selectedIcon
;
bool
get
_enabled
=>
onSelectionChanged
!=
null
;
void
_handleOnPressed
(
T
segmentValue
)
{
if
(!
_enabled
)
{
return
;
}
final
bool
onlySelectedSegment
=
selected
.
length
==
1
&&
selected
.
contains
(
segmentValue
);
final
bool
validChange
=
emptySelectionAllowed
||
!
onlySelectedSegment
;
if
(
validChange
)
{
final
bool
toggle
=
multiSelectionEnabled
||
(
emptySelectionAllowed
&&
onlySelectedSegment
);
final
Set
<
T
>
pressedSegment
=
<
T
>{
segmentValue
};
late
final
Set
<
T
>
updatedSelection
;
if
(
toggle
)
{
updatedSelection
=
selected
.
contains
(
segmentValue
)
?
selected
.
difference
(
pressedSegment
)
:
selected
.
union
(
pressedSegment
);
}
else
{
updatedSelection
=
pressedSegment
;
}
if
(!
setEquals
(
updatedSelection
,
selected
))
{
onSelectionChanged
!(
updatedSelection
);
}
}
}
@override
Widget
build
(
BuildContext
context
)
{
final
SegmentedButtonThemeData
theme
=
SegmentedButtonTheme
.
of
(
context
);
final
SegmentedButtonThemeData
defaults
=
_SegmentedButtonDefaultsM3
(
context
);
final
TextDirection
direction
=
Directionality
.
of
(
context
);
const
Set
<
MaterialState
>
enabledState
=
<
MaterialState
>{};
const
Set
<
MaterialState
>
disabledState
=
<
MaterialState
>{
MaterialState
.
disabled
};
final
Set
<
MaterialState
>
currentState
=
_enabled
?
enabledState
:
disabledState
;
P
?
effectiveValue
<
P
>(
P
?
Function
(
ButtonStyle
?
style
)
getProperty
)
{
late
final
P
?
widgetValue
=
getProperty
(
style
);
late
final
P
?
themeValue
=
getProperty
(
theme
.
style
);
late
final
P
?
defaultValue
=
getProperty
(
defaults
.
style
);
return
widgetValue
??
themeValue
??
defaultValue
;
}
P
?
resolve
<
P
>(
MaterialStateProperty
<
P
>?
Function
(
ButtonStyle
?
style
)
getProperty
,
[
Set
<
MaterialState
>?
states
])
{
return
effectiveValue
(
(
ButtonStyle
?
style
)
=>
getProperty
(
style
)?.
resolve
(
states
??
currentState
),
);
}
ButtonStyle
segmentStyleFor
(
ButtonStyle
?
style
)
{
return
ButtonStyle
(
textStyle:
style
?.
textStyle
,
backgroundColor:
style
?.
backgroundColor
,
foregroundColor:
style
?.
foregroundColor
,
overlayColor:
style
?.
overlayColor
,
surfaceTintColor:
style
?.
surfaceTintColor
,
elevation:
style
?.
elevation
,
padding:
style
?.
padding
,
iconColor:
style
?.
iconColor
,
iconSize:
style
?.
iconSize
,
shape:
const
MaterialStatePropertyAll
<
OutlinedBorder
>(
RoundedRectangleBorder
()),
mouseCursor:
style
?.
mouseCursor
,
visualDensity:
style
?.
visualDensity
,
tapTargetSize:
style
?.
tapTargetSize
,
animationDuration:
style
?.
animationDuration
,
enableFeedback:
style
?.
enableFeedback
,
alignment:
style
?.
alignment
,
splashFactory:
style
?.
splashFactory
,
);
}
final
ButtonStyle
segmentStyle
=
segmentStyleFor
(
style
);
final
ButtonStyle
segmentThemeStyle
=
segmentStyleFor
(
theme
.
style
).
merge
(
segmentStyleFor
(
defaults
.
style
));
final
Widget
?
selectedIcon
=
showSelectedIcon
?
this
.
selectedIcon
??
theme
.
selectedIcon
??
defaults
.
selectedIcon
:
null
;
Widget
buttonFor
(
ButtonSegment
<
T
>
segment
)
{
final
Widget
label
=
segment
.
label
??
segment
.
icon
??
const
SizedBox
.
shrink
();
final
bool
segmentSelected
=
selected
.
contains
(
segment
.
value
);
final
Widget
?
icon
=
(
segmentSelected
&&
showSelectedIcon
)
?
selectedIcon
:
segment
.
label
!=
null
?
segment
.
icon
:
null
;
final
MaterialStatesController
controller
=
MaterialStatesController
(
<
MaterialState
>{
if
(
segmentSelected
)
MaterialState
.
selected
,
}
);
final
Widget
button
=
icon
!=
null
?
TextButton
.
icon
(
style:
segmentStyle
,
statesController:
controller
,
onPressed:
(
_enabled
&&
segment
.
enabled
)
?
()
=>
_handleOnPressed
(
segment
.
value
)
:
null
,
icon:
icon
,
label:
label
,
)
:
TextButton
(
style:
segmentStyle
,
statesController:
controller
,
onPressed:
(
_enabled
&&
segment
.
enabled
)
?
()
=>
_handleOnPressed
(
segment
.
value
)
:
null
,
child:
label
,
);
return
MergeSemantics
(
child:
Semantics
(
checked:
segmentSelected
,
inMutuallyExclusiveGroup:
multiSelectionEnabled
?
null
:
true
,
child:
button
,
),
);
}
final
OutlinedBorder
resolvedEnabledBorder
=
resolve
<
OutlinedBorder
?>((
ButtonStyle
?
style
)
=>
style
?.
shape
,
disabledState
)
??
const
RoundedRectangleBorder
();
final
OutlinedBorder
resolvedDisabledBorder
=
resolve
<
OutlinedBorder
?>((
ButtonStyle
?
style
)
=>
style
?.
shape
,
disabledState
)??
const
RoundedRectangleBorder
();
final
BorderSide
enabledSide
=
resolve
<
BorderSide
?>((
ButtonStyle
?
style
)
=>
style
?.
side
,
enabledState
)
??
BorderSide
.
none
;
final
BorderSide
disabledSide
=
resolve
<
BorderSide
?>((
ButtonStyle
?
style
)
=>
style
?.
side
,
disabledState
)
??
BorderSide
.
none
;
final
OutlinedBorder
enabledBorder
=
resolvedEnabledBorder
.
copyWith
(
side:
enabledSide
);
final
OutlinedBorder
disabledBorder
=
resolvedDisabledBorder
.
copyWith
(
side:
disabledSide
);
final
List
<
Widget
>
buttons
=
segments
.
map
(
buttonFor
).
toList
();
return
Material
(
shape:
enabledBorder
.
copyWith
(
side:
BorderSide
.
none
),
elevation:
resolve
<
double
?>((
ButtonStyle
?
style
)
=>
style
?.
elevation
)!,
shadowColor:
resolve
<
Color
?>((
ButtonStyle
?
style
)
=>
style
?.
shadowColor
),
surfaceTintColor:
resolve
<
Color
?>((
ButtonStyle
?
style
)
=>
style
?.
surfaceTintColor
),
child:
TextButtonTheme
(
data:
TextButtonThemeData
(
style:
segmentThemeStyle
),
child:
_SegmentedButtonRenderWidget
<
T
>(
segments:
segments
,
enabledBorder:
_enabled
?
enabledBorder
:
disabledBorder
,
disabledBorder:
disabledBorder
,
direction:
direction
,
children:
buttons
,
),
),
);
}
}
class
_SegmentedButtonRenderWidget
<
T
>
extends
MultiChildRenderObjectWidget
{
_SegmentedButtonRenderWidget
({
super
.
key
,
required
this
.
segments
,
required
this
.
enabledBorder
,
required
this
.
disabledBorder
,
required
this
.
direction
,
required
super
.
children
,
})
:
assert
(
children
.
length
==
segments
.
length
);
final
List
<
ButtonSegment
<
T
>>
segments
;
final
OutlinedBorder
enabledBorder
;
final
OutlinedBorder
disabledBorder
;
final
TextDirection
direction
;
@override
RenderObject
createRenderObject
(
BuildContext
context
)
{
return
_RenderSegmentedButton
<
T
>(
segments:
segments
,
enabledBorder:
enabledBorder
,
disabledBorder:
disabledBorder
,
textDirection:
direction
,
);
}
@override
void
updateRenderObject
(
BuildContext
context
,
_RenderSegmentedButton
<
T
>
renderObject
)
{
renderObject
..
segments
=
segments
..
enabledBorder
=
enabledBorder
..
disabledBorder
=
disabledBorder
..
textDirection
=
direction
;
}
}
class
_SegmentedButtonContainerBoxParentData
extends
ContainerBoxParentData
<
RenderBox
>
{
RRect
?
surroundingRect
;
}
typedef
_NextChild
=
RenderBox
?
Function
(
RenderBox
child
);
class
_RenderSegmentedButton
<
T
>
extends
RenderBox
with
ContainerRenderObjectMixin
<
RenderBox
,
ContainerBoxParentData
<
RenderBox
>>,
RenderBoxContainerDefaultsMixin
<
RenderBox
,
ContainerBoxParentData
<
RenderBox
>>
{
_RenderSegmentedButton
({
required
List
<
ButtonSegment
<
T
>>
segments
,
required
OutlinedBorder
enabledBorder
,
required
OutlinedBorder
disabledBorder
,
required
TextDirection
textDirection
,
})
:
_segments
=
segments
,
_enabledBorder
=
enabledBorder
,
_disabledBorder
=
disabledBorder
,
_textDirection
=
textDirection
;
List
<
ButtonSegment
<
T
>>
get
segments
=>
_segments
;
List
<
ButtonSegment
<
T
>>
_segments
;
set
segments
(
List
<
ButtonSegment
<
T
>>
value
)
{
if
(
listEquals
(
segments
,
value
))
{
return
;
}
_segments
=
value
;
markNeedsLayout
();
}
OutlinedBorder
get
enabledBorder
=>
_enabledBorder
;
OutlinedBorder
_enabledBorder
;
set
enabledBorder
(
OutlinedBorder
value
)
{
if
(
_enabledBorder
==
value
)
{
return
;
}
_enabledBorder
=
value
;
markNeedsLayout
();
}
OutlinedBorder
get
disabledBorder
=>
_disabledBorder
;
OutlinedBorder
_disabledBorder
;
set
disabledBorder
(
OutlinedBorder
value
)
{
if
(
_disabledBorder
==
value
)
{
return
;
}
_disabledBorder
=
value
;
markNeedsLayout
();
}
TextDirection
get
textDirection
=>
_textDirection
;
TextDirection
_textDirection
;
set
textDirection
(
TextDirection
value
)
{
if
(
value
==
_textDirection
)
{
return
;
}
_textDirection
=
value
;
markNeedsLayout
();
}
@override
double
computeMinIntrinsicWidth
(
double
height
)
{
RenderBox
?
child
=
firstChild
;
double
minWidth
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedButtonContainerBoxParentData
childParentData
=
child
.
parentData
!
as
_SegmentedButtonContainerBoxParentData
;
final
double
childWidth
=
child
.
getMinIntrinsicWidth
(
height
);
minWidth
=
math
.
max
(
minWidth
,
childWidth
);
child
=
childParentData
.
nextSibling
;
}
return
minWidth
*
childCount
;
}
@override
double
computeMaxIntrinsicWidth
(
double
height
)
{
RenderBox
?
child
=
firstChild
;
double
maxWidth
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedButtonContainerBoxParentData
childParentData
=
child
.
parentData
!
as
_SegmentedButtonContainerBoxParentData
;
final
double
childWidth
=
child
.
getMaxIntrinsicWidth
(
height
);
maxWidth
=
math
.
max
(
maxWidth
,
childWidth
);
child
=
childParentData
.
nextSibling
;
}
return
maxWidth
*
childCount
;
}
@override
double
computeMinIntrinsicHeight
(
double
width
)
{
RenderBox
?
child
=
firstChild
;
double
minHeight
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedButtonContainerBoxParentData
childParentData
=
child
.
parentData
!
as
_SegmentedButtonContainerBoxParentData
;
final
double
childHeight
=
child
.
getMinIntrinsicHeight
(
width
);
minHeight
=
math
.
max
(
minHeight
,
childHeight
);
child
=
childParentData
.
nextSibling
;
}
return
minHeight
;
}
@override
double
computeMaxIntrinsicHeight
(
double
width
)
{
RenderBox
?
child
=
firstChild
;
double
maxHeight
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedButtonContainerBoxParentData
childParentData
=
child
.
parentData
!
as
_SegmentedButtonContainerBoxParentData
;
final
double
childHeight
=
child
.
getMaxIntrinsicHeight
(
width
);
maxHeight
=
math
.
max
(
maxHeight
,
childHeight
);
child
=
childParentData
.
nextSibling
;
}
return
maxHeight
;
}
@override
double
?
computeDistanceToActualBaseline
(
TextBaseline
baseline
)
{
return
defaultComputeDistanceToHighestActualBaseline
(
baseline
);
}
@override
void
setupParentData
(
RenderBox
child
)
{
if
(
child
.
parentData
is
!
_SegmentedButtonContainerBoxParentData
)
{
child
.
parentData
=
_SegmentedButtonContainerBoxParentData
();
}
}
void
_layoutRects
(
_NextChild
nextChild
,
RenderBox
?
leftChild
,
RenderBox
?
rightChild
)
{
RenderBox
?
child
=
leftChild
;
double
start
=
0.0
;
while
(
child
!=
null
)
{
final
_SegmentedButtonContainerBoxParentData
childParentData
=
child
.
parentData
!
as
_SegmentedButtonContainerBoxParentData
;
final
Offset
childOffset
=
Offset
(
start
,
0.0
);
childParentData
.
offset
=
childOffset
;
final
Rect
childRect
=
Rect
.
fromLTWH
(
start
,
0.0
,
child
.
size
.
width
,
child
.
size
.
height
);
final
RRect
rChildRect
=
RRect
.
fromRectAndCorners
(
childRect
);
childParentData
.
surroundingRect
=
rChildRect
;
start
+=
child
.
size
.
width
;
child
=
nextChild
(
child
);
}
}
Size
_calculateChildSize
(
BoxConstraints
constraints
)
{
double
maxHeight
=
0
;
double
childWidth
=
constraints
.
minWidth
/
childCount
;
RenderBox
?
child
=
firstChild
;
while
(
child
!=
null
)
{
childWidth
=
math
.
max
(
childWidth
,
child
.
getMaxIntrinsicWidth
(
double
.
infinity
));
child
=
childAfter
(
child
);
}
childWidth
=
math
.
min
(
childWidth
,
constraints
.
maxWidth
/
childCount
);
child
=
firstChild
;
while
(
child
!=
null
)
{
final
double
boxHeight
=
child
.
getMaxIntrinsicHeight
(
childWidth
);
maxHeight
=
math
.
max
(
maxHeight
,
boxHeight
);
child
=
childAfter
(
child
);
}
return
Size
(
childWidth
,
maxHeight
);
}
Size
_computeOverallSizeFromChildSize
(
Size
childSize
)
{
return
constraints
.
constrain
(
Size
(
childSize
.
width
*
childCount
,
childSize
.
height
));
}
@override
Size
computeDryLayout
(
BoxConstraints
constraints
)
{
final
Size
childSize
=
_calculateChildSize
(
constraints
);
return
_computeOverallSizeFromChildSize
(
childSize
);
}
@override
void
performLayout
()
{
final
BoxConstraints
constraints
=
this
.
constraints
;
final
Size
childSize
=
_calculateChildSize
(
constraints
);
final
BoxConstraints
childConstraints
=
BoxConstraints
.
tightFor
(
width:
childSize
.
width
,
height:
childSize
.
height
,
);
RenderBox
?
child
=
firstChild
;
while
(
child
!=
null
)
{
child
.
layout
(
childConstraints
,
parentUsesSize:
true
);
child
=
childAfter
(
child
);
}
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
_layoutRects
(
childBefore
,
lastChild
,
firstChild
,
);
break
;
case
TextDirection
.
ltr
:
_layoutRects
(
childAfter
,
firstChild
,
lastChild
,
);
break
;
}
size
=
_computeOverallSizeFromChildSize
(
childSize
);
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
final
Canvas
canvas
=
context
.
canvas
;
final
Rect
borderRect
=
offset
&
size
;
final
Path
borderClipPath
=
enabledBorder
.
getInnerPath
(
borderRect
,
textDirection:
textDirection
);
RenderBox
?
child
=
firstChild
;
RenderBox
?
previousChild
;
int
index
=
0
;
Path
?
enabledClipPath
;
Path
?
disabledClipPath
;
canvas
..
save
()..
clipPath
(
borderClipPath
);
while
(
child
!=
null
)
{
final
_SegmentedButtonContainerBoxParentData
childParentData
=
child
.
parentData
!
as
_SegmentedButtonContainerBoxParentData
;
final
Rect
childRect
=
childParentData
.
surroundingRect
!.
outerRect
.
shift
(
offset
);
canvas
..
save
()..
clipRect
(
childRect
);
context
.
paintChild
(
child
,
childParentData
.
offset
+
offset
);
canvas
.
restore
();
// Compute a clip rect for the outer border of the child.
late
final
double
segmentLeft
;
late
final
double
segmentRight
;
late
final
double
dividerPos
;
final
double
borderOutset
=
math
.
max
(
enabledBorder
.
side
.
strokeOutset
,
disabledBorder
.
side
.
strokeOutset
);
switch
(
textDirection
)
{
case
TextDirection
.
rtl
:
segmentLeft
=
child
==
lastChild
?
borderRect
.
left
-
borderOutset
:
childRect
.
left
;
segmentRight
=
child
==
firstChild
?
borderRect
.
right
+
borderOutset
:
childRect
.
right
;
dividerPos
=
segmentRight
;
break
;
case
TextDirection
.
ltr
:
segmentLeft
=
child
==
firstChild
?
borderRect
.
left
-
borderOutset
:
childRect
.
left
;
segmentRight
=
child
==
lastChild
?
borderRect
.
right
+
borderOutset
:
childRect
.
right
;
dividerPos
=
segmentLeft
;
break
;
}
final
Rect
segmentClipRect
=
Rect
.
fromLTRB
(
segmentLeft
,
borderRect
.
top
-
borderOutset
,
segmentRight
,
borderRect
.
bottom
+
borderOutset
);
// Add the clip rect to the appropriate border clip path
if
(
segments
[
index
].
enabled
)
{
enabledClipPath
=
(
enabledClipPath
??
Path
())..
addRect
(
segmentClipRect
);
}
else
{
disabledClipPath
=
(
disabledClipPath
??
Path
())..
addRect
(
segmentClipRect
);
}
// Paint the divider between this segment and the previous one.
if
(
previousChild
!=
null
)
{
final
BorderSide
divider
=
segments
[
index
-
1
].
enabled
||
segments
[
index
].
enabled
?
enabledBorder
.
side
.
copyWith
(
strokeAlign:
0.0
)
:
disabledBorder
.
side
.
copyWith
(
strokeAlign:
0.0
);
final
Offset
top
=
Offset
(
dividerPos
,
childRect
.
top
);
final
Offset
bottom
=
Offset
(
dividerPos
,
childRect
.
bottom
);
canvas
.
drawLine
(
top
,
bottom
,
divider
.
toPaint
());
}
previousChild
=
child
;
child
=
childAfter
(
child
);
index
+=
1
;
}
canvas
.
restore
();
// Paint the outer border for both disabled and enabled clip rect if needed.
if
(
disabledClipPath
==
null
)
{
// Just paint the enabled border with no clip.
enabledBorder
.
paint
(
context
.
canvas
,
borderRect
,
textDirection:
textDirection
);
}
else
if
(
enabledClipPath
==
null
)
{
// Just paint the disabled border with no.
disabledBorder
.
paint
(
context
.
canvas
,
borderRect
,
textDirection:
textDirection
);
}
else
{
// Paint both of them clipped appropriately for the children segments.
canvas
..
save
()..
clipPath
(
enabledClipPath
);
enabledBorder
.
paint
(
context
.
canvas
,
borderRect
,
textDirection:
textDirection
);
canvas
..
restore
()..
save
()..
clipPath
(
disabledClipPath
);
disabledBorder
.
paint
(
context
.
canvas
,
borderRect
,
textDirection:
textDirection
);
canvas
.
restore
();
}
}
@override
bool
hitTestChildren
(
BoxHitTestResult
result
,
{
required
Offset
position
})
{
assert
(
position
!=
null
);
RenderBox
?
child
=
lastChild
;
while
(
child
!=
null
)
{
final
_SegmentedButtonContainerBoxParentData
childParentData
=
child
.
parentData
!
as
_SegmentedButtonContainerBoxParentData
;
if
(
childParentData
.
surroundingRect
!.
contains
(
position
))
{
return
result
.
addWithPaintOffset
(
offset:
childParentData
.
offset
,
position:
position
,
hitTest:
(
BoxHitTestResult
result
,
Offset
localOffset
)
{
assert
(
localOffset
==
position
-
childParentData
.
offset
);
return
child
!.
hitTest
(
result
,
position:
localOffset
);
},
);
}
child
=
childParentData
.
previousSibling
;
}
return
false
;
}
}
// BEGIN GENERATED TOKEN PROPERTIES - SegmentedButton
// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
// dev/tools/gen_defaults/bin/gen_defaults.dart.
// Token database version: v0_137
class
_SegmentedButtonDefaultsM3
extends
SegmentedButtonThemeData
{
_SegmentedButtonDefaultsM3
(
this
.
context
);
final
BuildContext
context
;
late
final
ThemeData
_theme
=
Theme
.
of
(
context
);
late
final
ColorScheme
_colors
=
_theme
.
colorScheme
;
@override
ButtonStyle
?
get
style
{
return
ButtonStyle
(
textStyle:
MaterialStatePropertyAll
<
TextStyle
?>(
Theme
.
of
(
context
).
textTheme
.
labelLarge
),
backgroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
null
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
_colors
.
secondaryContainer
;
}
return
null
;
}),
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.38
);
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onSecondaryContainer
;
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSecondaryContainer
;
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onSecondaryContainer
;
}
return
_colors
.
onSecondaryContainer
;
}
else
{
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onSurface
;
}
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSurface
;
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onSurface
;
}
return
null
;
}
}),
overlayColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
selected
))
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSecondaryContainer
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onSecondaryContainer
.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onSecondaryContainer
.
withOpacity
(
0.12
);
}
}
else
{
if
(
states
.
contains
(
MaterialState
.
hovered
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.08
);
}
if
(
states
.
contains
(
MaterialState
.
focused
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
if
(
states
.
contains
(
MaterialState
.
pressed
))
{
return
_colors
.
onSurface
.
withOpacity
(
0.12
);
}
}
return
null
;
}),
surfaceTintColor:
const
MaterialStatePropertyAll
<
Color
>(
Colors
.
transparent
),
elevation:
const
MaterialStatePropertyAll
<
double
>(
0
),
iconSize:
const
MaterialStatePropertyAll
<
double
?>(
18.0
),
side:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
BorderSide
(
color:
_colors
.
onSurface
.
withOpacity
(
0.12
));
}
return
BorderSide
(
color:
_colors
.
outline
);
}),
shape:
const
MaterialStatePropertyAll
<
OutlinedBorder
>(
StadiumBorder
()),
);
}
@override
Widget
?
get
selectedIcon
=>
const
Icon
(
Icons
.
check
);
}
// END GENERATED TOKEN PROPERTIES - SegmentedButton
packages/flutter/lib/src/material/segmented_button_theme.dart
0 → 100644
View file @
6ec2bd0a
// 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/foundation.dart'
;
import
'package:flutter/widgets.dart'
;
import
'button_style.dart'
;
import
'theme.dart'
;
// Examples can assume:
// late BuildContext context;
/// Overrides the default values of visual properties for descendant
/// [SegmentedButton] widgets.
///
/// Descendant widgets obtain the current [SegmentedButtonThemeData] object with
/// [SegmentedButtonTheme.of]. Instances of [SegmentedButtonTheme] can
/// be customized with [SegmentedButtonThemeData.copyWith].
///
/// Typically a [SegmentedButtonTheme] is specified as part of the overall
/// [Theme] with [ThemeData.segmentedButtonTheme].
///
/// All [SegmentedButtonThemeData] properties are null by default. When null,
/// the [SegmentedButton] computes its own default values, typically based on
/// the overall theme's [ThemeData.colorScheme], [ThemeData.textTheme], and
/// [ThemeData.iconTheme].
@immutable
class
SegmentedButtonThemeData
with
Diagnosticable
{
/// Creates a [SegmentedButtonThemeData] that can be used to override default properties
/// in a [SegmentedButtonTheme] widget.
const
SegmentedButtonThemeData
({
this
.
style
,
this
.
selectedIcon
,
});
/// Overrides the [SegmentedButton]'s default style.
///
/// Non-null properties or non-null resolved [MaterialStateProperty]
/// values override the default values used by [SegmentedButton].
///
/// If [style] is null, then this theme doesn't override anything.
final
ButtonStyle
?
style
;
/// Override for [SegmentedButton.selectedIcon] property.
///
/// If non-null, then [selectedIcon] will be used instead of default
/// value for [SegmentedButton.selectedIcon].
final
Widget
?
selectedIcon
;
/// Creates a copy of this object with the given fields replaced with the
/// new values.
SegmentedButtonThemeData
copyWith
({
ButtonStyle
?
style
,
Widget
?
selectedIcon
,
})
{
return
SegmentedButtonThemeData
(
style:
style
??
this
.
style
,
selectedIcon:
selectedIcon
??
this
.
selectedIcon
,
);
}
/// Linearly interpolates between two segmented button themes.
static
SegmentedButtonThemeData
lerp
(
SegmentedButtonThemeData
?
a
,
SegmentedButtonThemeData
?
b
,
double
t
)
{
return
SegmentedButtonThemeData
(
style:
ButtonStyle
.
lerp
(
a
?.
style
,
b
?.
style
,
t
),
selectedIcon:
t
<
0.5
?
a
?.
selectedIcon
:
b
?.
selectedIcon
,
);
}
@override
int
get
hashCode
=>
Object
.
hash
(
style
,
selectedIcon
,
);
@override
bool
operator
==(
Object
other
)
{
if
(
identical
(
this
,
other
))
{
return
true
;
}
if
(
other
.
runtimeType
!=
runtimeType
)
{
return
false
;
}
return
other
is
SegmentedButtonThemeData
&&
other
.
style
==
style
&&
other
.
selectedIcon
==
selectedIcon
;
}
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
properties
)
{
super
.
debugFillProperties
(
properties
);
properties
.
add
(
DiagnosticsProperty
<
ButtonStyle
>(
'style'
,
style
,
defaultValue:
null
));
properties
.
add
(
DiagnosticsProperty
<
Widget
>(
'selectedIcon'
,
selectedIcon
,
defaultValue:
null
));
}
}
/// An inherited widget that defines the visual properties for
/// [SegmentedButton]s in this widget's subtree.
///
/// Values specified here are used for [SegmentedButton] properties that are not
/// given an explicit non-null value.
class
SegmentedButtonTheme
extends
InheritedTheme
{
/// Creates a [SegmentedButtonTheme] that controls visual parameters for
/// descendent [SegmentedButton]s.
const
SegmentedButtonTheme
({
super
.
key
,
required
this
.
data
,
required
super
.
child
,
})
:
assert
(
data
!=
null
);
/// Specifies the visual properties used by descendant [SegmentedButton]
/// widgets.
final
SegmentedButtonThemeData
data
;
/// The [data] from the closest instance of this class that encloses the given
/// context.
///
/// If there is no [SegmentedButtonTheme] in scope, this will return
/// [ThemeData.segmentedButtonTheme] from the ambient [Theme].
///
/// Typical usage is as follows:
///
/// ```dart
/// SegmentedButtonThemeData theme = SegmentedButtonTheme.of(context);
/// ```
///
/// See also:
///
/// * [maybeOf], which returns null if it doesn't find a
/// [SegmentedButtonTheme] ancestor.
static
SegmentedButtonThemeData
of
(
BuildContext
context
)
{
return
maybeOf
(
context
)
??
Theme
.
of
(
context
).
segmentedButtonTheme
;
}
/// The data from the closest instance of this class that encloses the given
/// context, if any.
///
/// Use this function if you want to allow situations where no
/// [SegmentedButtonTheme] is in scope. Prefer using [SegmentedButtonTheme.of]
/// in situations where a [SegmentedButtonThemeData] is expected to be
/// non-null.
///
/// If there is no [SegmentedButtonTheme] in scope, then this function will
/// return null.
///
/// Typical usage is as follows:
///
/// ```dart
/// SegmentedButtonThemeData? theme = SegmentedButtonTheme.maybeOf(context);
/// if (theme == null) {
/// // Do something else instead.
/// }
/// ```
///
/// See also:
///
/// * [of], which will return [ThemeData.segmentedButtonTheme] if it doesn't
/// find a [SegmentedButtonTheme] ancestor, instead of returning null.
static
SegmentedButtonThemeData
?
maybeOf
(
BuildContext
context
)
{
assert
(
context
!=
null
);
return
context
.
dependOnInheritedWidgetOfExactType
<
SegmentedButtonTheme
>()?.
data
;
}
@override
Widget
wrap
(
BuildContext
context
,
Widget
child
)
{
return
SegmentedButtonTheme
(
data:
data
,
child:
child
);
}
@override
bool
updateShouldNotify
(
SegmentedButtonTheme
oldWidget
)
=>
data
!=
oldWidget
.
data
;
}
packages/flutter/lib/src/material/theme_data.dart
View file @
6ec2bd0a
...
...
@@ -48,6 +48,7 @@ import 'popup_menu_theme.dart';
import
'progress_indicator_theme.dart'
;
import
'radio_theme.dart'
;
import
'scrollbar_theme.dart'
;
import
'segmented_button_theme.dart'
;
import
'slider_theme.dart'
;
import
'snack_bar_theme.dart'
;
import
'switch_theme.dart'
;
...
...
@@ -361,6 +362,7 @@ class ThemeData with Diagnosticable {
PopupMenuThemeData
?
popupMenuTheme
,
ProgressIndicatorThemeData
?
progressIndicatorTheme
,
RadioThemeData
?
radioTheme
,
SegmentedButtonThemeData
?
segmentedButtonTheme
,
SliderThemeData
?
sliderTheme
,
SnackBarThemeData
?
snackBarTheme
,
SwitchThemeData
?
switchTheme
,
...
...
@@ -613,6 +615,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme
??=
const
PopupMenuThemeData
();
progressIndicatorTheme
??=
const
ProgressIndicatorThemeData
();
radioTheme
??=
const
RadioThemeData
();
segmentedButtonTheme
??=
const
SegmentedButtonThemeData
();
sliderTheme
??=
const
SliderThemeData
();
snackBarTheme
??=
const
SnackBarThemeData
();
switchTheme
??=
const
SwitchThemeData
();
...
...
@@ -708,6 +711,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme:
popupMenuTheme
,
progressIndicatorTheme:
progressIndicatorTheme
,
radioTheme:
radioTheme
,
segmentedButtonTheme:
segmentedButtonTheme
,
sliderTheme:
sliderTheme
,
snackBarTheme:
snackBarTheme
,
switchTheme:
switchTheme
,
...
...
@@ -819,6 +823,7 @@ class ThemeData with Diagnosticable {
required
this
.
popupMenuTheme
,
required
this
.
progressIndicatorTheme
,
required
this
.
radioTheme
,
required
this
.
segmentedButtonTheme
,
required
this
.
sliderTheme
,
required
this
.
snackBarTheme
,
required
this
.
switchTheme
,
...
...
@@ -988,6 +993,7 @@ class ThemeData with Diagnosticable {
assert
(
popupMenuTheme
!=
null
),
assert
(
progressIndicatorTheme
!=
null
),
assert
(
radioTheme
!=
null
),
assert
(
segmentedButtonTheme
!=
null
),
assert
(
sliderTheme
!=
null
),
assert
(
snackBarTheme
!=
null
),
assert
(
switchTheme
!=
null
),
...
...
@@ -1252,10 +1258,8 @@ class ThemeData with Diagnosticable {
/// A temporary flag used to opt-in to Material 3 features.
///
/// If true, then widgets that have been migrated to Material 3 will
/// use new colors, typography and other features of Material 3. A new
/// purple-based [ColorScheme] will be created and applied to the updated
/// widgets, as long as this is set to true. If false, they will use the
/// Material 2 look and feel.
/// use new colors, typography and other features of Material 3. If false,
/// they will use the Material 2 look and feel.
///
/// During the migration to Material 3, turning this on may yield
/// inconsistent look and feel in your app as some widgets are migrated
...
...
@@ -1293,10 +1297,11 @@ class ThemeData with Diagnosticable {
/// * Typography: `typography` (see table above)
///
/// ### Components
/// * Common buttons: [ElevatedButton], [FilledButton], [OutlinedButton], [TextButton], [IconButton]
/// * Bottom app bar: [BottomAppBar]
/// * FAB: [FloatingActionButton]
/// * Extended FAB: [FloatingActionButton.extended]
/// * Buttons
/// - Common buttons: [ElevatedButton], [FilledButton], [OutlinedButton], [TextButton], [IconButton]
/// - FAB: [FloatingActionButton], [FloatingActionButton.extended]
/// - Segmented buttons: [SegmentedButton]
/// * Cards: [Card]
/// * TextFields: [TextField] together with its [InputDecoration]
/// * Chips:
...
...
@@ -1599,6 +1604,9 @@ class ThemeData with Diagnosticable {
/// A theme for customizing the appearance and layout of [Radio] widgets.
final
RadioThemeData
radioTheme
;
/// A theme for customizing the appearance and layout of [SegmentedButton] widgets.
final
SegmentedButtonThemeData
segmentedButtonTheme
;
/// The colors and shapes used to render [Slider].
///
/// This is the value returned from [SliderTheme.of].
...
...
@@ -1880,6 +1888,7 @@ class ThemeData with Diagnosticable {
PopupMenuThemeData
?
popupMenuTheme
,
ProgressIndicatorThemeData
?
progressIndicatorTheme
,
RadioThemeData
?
radioTheme
,
SegmentedButtonThemeData
?
segmentedButtonTheme
,
SliderThemeData
?
sliderTheme
,
SnackBarThemeData
?
snackBarTheme
,
SwitchThemeData
?
switchTheme
,
...
...
@@ -2042,6 +2051,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme:
popupMenuTheme
??
this
.
popupMenuTheme
,
progressIndicatorTheme:
progressIndicatorTheme
??
this
.
progressIndicatorTheme
,
radioTheme:
radioTheme
??
this
.
radioTheme
,
segmentedButtonTheme:
segmentedButtonTheme
??
this
.
segmentedButtonTheme
,
sliderTheme:
sliderTheme
??
this
.
sliderTheme
,
snackBarTheme:
snackBarTheme
??
this
.
snackBarTheme
,
switchTheme:
switchTheme
??
this
.
switchTheme
,
...
...
@@ -2246,6 +2256,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme:
PopupMenuThemeData
.
lerp
(
a
.
popupMenuTheme
,
b
.
popupMenuTheme
,
t
)!,
progressIndicatorTheme:
ProgressIndicatorThemeData
.
lerp
(
a
.
progressIndicatorTheme
,
b
.
progressIndicatorTheme
,
t
)!,
radioTheme:
RadioThemeData
.
lerp
(
a
.
radioTheme
,
b
.
radioTheme
,
t
),
segmentedButtonTheme:
SegmentedButtonThemeData
.
lerp
(
a
.
segmentedButtonTheme
,
b
.
segmentedButtonTheme
,
t
),
sliderTheme:
SliderThemeData
.
lerp
(
a
.
sliderTheme
,
b
.
sliderTheme
,
t
),
snackBarTheme:
SnackBarThemeData
.
lerp
(
a
.
snackBarTheme
,
b
.
snackBarTheme
,
t
),
switchTheme:
SwitchThemeData
.
lerp
(
a
.
switchTheme
,
b
.
switchTheme
,
t
),
...
...
@@ -2352,6 +2363,7 @@ class ThemeData with Diagnosticable {
other
.
popupMenuTheme
==
popupMenuTheme
&&
other
.
progressIndicatorTheme
==
progressIndicatorTheme
&&
other
.
radioTheme
==
radioTheme
&&
other
.
segmentedButtonTheme
==
segmentedButtonTheme
&&
other
.
sliderTheme
==
sliderTheme
&&
other
.
snackBarTheme
==
snackBarTheme
&&
other
.
switchTheme
==
switchTheme
&&
...
...
@@ -2455,6 +2467,7 @@ class ThemeData with Diagnosticable {
popupMenuTheme
,
progressIndicatorTheme
,
radioTheme
,
segmentedButtonTheme
,
sliderTheme
,
snackBarTheme
,
switchTheme
,
...
...
@@ -2560,6 +2573,7 @@ class ThemeData with Diagnosticable {
properties
.
add
(
DiagnosticsProperty
<
PopupMenuThemeData
>(
'popupMenuTheme'
,
popupMenuTheme
,
defaultValue:
defaultData
.
popupMenuTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
ProgressIndicatorThemeData
>(
'progressIndicatorTheme'
,
progressIndicatorTheme
,
defaultValue:
defaultData
.
progressIndicatorTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
RadioThemeData
>(
'radioTheme'
,
radioTheme
,
defaultValue:
defaultData
.
radioTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
SegmentedButtonThemeData
>(
'segmentedButtonTheme'
,
segmentedButtonTheme
,
defaultValue:
defaultData
.
segmentedButtonTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
SliderThemeData
>(
'sliderTheme'
,
sliderTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
SnackBarThemeData
>(
'snackBarTheme'
,
snackBarTheme
,
defaultValue:
defaultData
.
snackBarTheme
,
level:
DiagnosticLevel
.
debug
));
properties
.
add
(
DiagnosticsProperty
<
SwitchThemeData
>(
'switchTheme'
,
switchTheme
,
defaultValue:
defaultData
.
switchTheme
,
level:
DiagnosticLevel
.
debug
));
...
...
packages/flutter/test/material/segmented_button_test.dart
0 → 100644
View file @
6ec2bd0a
// 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.
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
import
'package:flutter/material.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'../widgets/semantics_tester.dart'
;
Widget
boilerplate
(
{
required
Widget
child
})
{
return
Directionality
(
textDirection:
TextDirection
.
ltr
,
child:
Center
(
child:
child
),
);
}
void
main
(
)
{
testWidgets
(
'SegmentedButton supports exclusive choice by default'
,
(
WidgetTester
tester
)
async
{
int
callbackCount
=
0
;
int
selectedSegment
=
2
;
Widget
frameWithSelection
(
int
selected
)
{
return
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
)),
],
selected:
<
int
>{
selected
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{
assert
(
selected
.
length
==
1
);
selectedSegment
=
selected
.
first
;
callbackCount
+=
1
;
},
),
),
);
}
await
tester
.
pumpWidget
(
frameWithSelection
(
selectedSegment
));
expect
(
selectedSegment
,
2
);
expect
(
callbackCount
,
0
);
// Tap on segment 1.
await
tester
.
tap
(
find
.
text
(
'1'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
1
);
expect
(
selectedSegment
,
1
);
// Update the selection in the widget
await
tester
.
pumpWidget
(
frameWithSelection
(
1
));
// Tap on segment 1 again should do nothing.
await
tester
.
tap
(
find
.
text
(
'1'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
1
);
expect
(
selectedSegment
,
1
);
// Tap on segment 3.
await
tester
.
tap
(
find
.
text
(
'3'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
2
);
expect
(
selectedSegment
,
3
);
});
testWidgets
(
'SegmentedButton supports multiple selected segments'
,
(
WidgetTester
tester
)
async
{
int
callbackCount
=
0
;
Set
<
int
>
selection
=
<
int
>{
1
};
Widget
frameWithSelection
(
Set
<
int
>
selected
)
{
return
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
multiSelectionEnabled:
true
,
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
)),
],
selected:
selected
,
onSelectionChanged:
(
Set
<
int
>
selected
)
{
selection
=
selected
;
callbackCount
+=
1
;
},
),
),
);
}
await
tester
.
pumpWidget
(
frameWithSelection
(
selection
));
expect
(
selection
,
<
int
>{
1
});
expect
(
callbackCount
,
0
);
// Tap on segment 2.
await
tester
.
tap
(
find
.
text
(
'2'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
1
);
expect
(
selection
,
<
int
>{
1
,
2
});
// Update the selection in the widget
await
tester
.
pumpWidget
(
frameWithSelection
(<
int
>{
1
,
2
}));
await
tester
.
pumpAndSettle
();
// Tap on segment 1 again should remove it from selection.
await
tester
.
tap
(
find
.
text
(
'1'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
2
);
expect
(
selection
,
<
int
>{
2
});
// Update the selection in the widget
await
tester
.
pumpWidget
(
frameWithSelection
(<
int
>{
2
}));
await
tester
.
pumpAndSettle
();
// Tap on segment 3.
await
tester
.
tap
(
find
.
text
(
'3'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
3
);
expect
(
selection
,
<
int
>{
2
,
3
});
});
testWidgets
(
'SegmentedButton allows for empty selection'
,
(
WidgetTester
tester
)
async
{
int
callbackCount
=
0
;
int
?
selectedSegment
=
1
;
Widget
frameWithSelection
(
int
?
selected
)
{
return
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
emptySelectionAllowed:
true
,
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
)),
],
selected:
<
int
>{
if
(
selected
!=
null
)
selected
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{
selectedSegment
=
selected
.
isEmpty
?
null
:
selected
.
first
;
callbackCount
+=
1
;
},
),
),
);
}
await
tester
.
pumpWidget
(
frameWithSelection
(
selectedSegment
));
expect
(
selectedSegment
,
1
);
expect
(
callbackCount
,
0
);
// Tap on segment 1 should deselect it and make the selection empty.
await
tester
.
tap
(
find
.
text
(
'1'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
1
);
expect
(
selectedSegment
,
null
);
// Update the selection in the widget
await
tester
.
pumpWidget
(
frameWithSelection
(
null
));
// Tap on segment 2 should select it.
await
tester
.
tap
(
find
.
text
(
'2'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
2
);
expect
(
selectedSegment
,
2
);
// Update the selection in the widget
await
tester
.
pumpWidget
(
frameWithSelection
(
2
));
// Tap on segment 3.
await
tester
.
tap
(
find
.
text
(
'3'
));
await
tester
.
pumpAndSettle
();
expect
(
callbackCount
,
3
);
expect
(
selectedSegment
,
3
);
});
testWidgets
(
'SegmentedButton shows checkboxes for selected segments'
,
(
WidgetTester
tester
)
async
{
Widget
frameWithSelection
(
int
selected
)
{
return
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
)),
],
selected:
<
int
>{
selected
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{},
),
),
);
}
Finder
textHasIcon
(
String
text
,
IconData
icon
)
{
return
find
.
descendant
(
of:
find
.
widgetWithText
(
Row
,
text
),
matching:
find
.
byIcon
(
icon
)
);
}
await
tester
.
pumpWidget
(
frameWithSelection
(
1
));
expect
(
textHasIcon
(
'1'
,
Icons
.
check
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
check
),
findsOneWidget
);
await
tester
.
pumpWidget
(
frameWithSelection
(
2
));
expect
(
textHasIcon
(
'2'
,
Icons
.
check
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
check
),
findsOneWidget
);
await
tester
.
pumpWidget
(
frameWithSelection
(
2
));
expect
(
textHasIcon
(
'2'
,
Icons
.
check
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
check
),
findsOneWidget
);
});
testWidgets
(
'SegmentedButton shows selected checkboxes in place of icon if it has a label as well'
,
(
WidgetTester
tester
)
async
{
Widget
frameWithSelection
(
int
selected
)
{
return
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
icon:
Icon
(
Icons
.
add
),
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
icon:
Icon
(
Icons
.
add_a_photo
),
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
icon:
Icon
(
Icons
.
add_alarm
),
label:
Text
(
'3'
)),
],
selected:
<
int
>{
selected
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{},
),
),
);
}
Finder
textHasIcon
(
String
text
,
IconData
icon
)
{
return
find
.
descendant
(
of:
find
.
widgetWithText
(
Row
,
text
),
matching:
find
.
byIcon
(
icon
)
);
}
await
tester
.
pumpWidget
(
frameWithSelection
(
1
));
expect
(
textHasIcon
(
'1'
,
Icons
.
check
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
add
),
findsNothing
);
expect
(
textHasIcon
(
'2'
,
Icons
.
add_a_photo
),
findsOneWidget
);
expect
(
textHasIcon
(
'3'
,
Icons
.
add_alarm
),
findsOneWidget
);
await
tester
.
pumpWidget
(
frameWithSelection
(
2
));
expect
(
textHasIcon
(
'1'
,
Icons
.
add
),
findsOneWidget
);
expect
(
textHasIcon
(
'2'
,
Icons
.
check
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
add_a_photo
),
findsNothing
);
expect
(
textHasIcon
(
'3'
,
Icons
.
add_alarm
),
findsOneWidget
);
await
tester
.
pumpWidget
(
frameWithSelection
(
3
));
expect
(
textHasIcon
(
'1'
,
Icons
.
add
),
findsOneWidget
);
expect
(
textHasIcon
(
'2'
,
Icons
.
add_a_photo
),
findsOneWidget
);
expect
(
textHasIcon
(
'3'
,
Icons
.
check
),
findsOneWidget
);
expect
(
find
.
byIcon
(
Icons
.
add_alarm
),
findsNothing
);
});
testWidgets
(
'SegmentedButton shows selected checkboxes next to icon if there is no label'
,
(
WidgetTester
tester
)
async
{
Widget
frameWithSelection
(
int
selected
)
{
return
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
icon:
Icon
(
Icons
.
add
)),
ButtonSegment
<
int
>(
value:
2
,
icon:
Icon
(
Icons
.
add_a_photo
)),
ButtonSegment
<
int
>(
value:
3
,
icon:
Icon
(
Icons
.
add_alarm
)),
],
selected:
<
int
>{
selected
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{},
),
),
);
}
Finder
rowWithIcons
(
IconData
icon1
,
IconData
icon2
)
{
return
find
.
descendant
(
of:
find
.
widgetWithIcon
(
Row
,
icon1
),
matching:
find
.
byIcon
(
icon2
)
);
}
await
tester
.
pumpWidget
(
frameWithSelection
(
1
));
expect
(
rowWithIcons
(
Icons
.
add
,
Icons
.
check
),
findsOneWidget
);
expect
(
rowWithIcons
(
Icons
.
add_a_photo
,
Icons
.
check
),
findsNothing
);
expect
(
rowWithIcons
(
Icons
.
add_alarm
,
Icons
.
check
),
findsNothing
);
await
tester
.
pumpWidget
(
frameWithSelection
(
2
));
expect
(
rowWithIcons
(
Icons
.
add
,
Icons
.
check
),
findsNothing
);
expect
(
rowWithIcons
(
Icons
.
add_a_photo
,
Icons
.
check
),
findsOneWidget
);
expect
(
rowWithIcons
(
Icons
.
add_alarm
,
Icons
.
check
),
findsNothing
);
await
tester
.
pumpWidget
(
frameWithSelection
(
3
));
expect
(
rowWithIcons
(
Icons
.
add
,
Icons
.
check
),
findsNothing
);
expect
(
rowWithIcons
(
Icons
.
add_a_photo
,
Icons
.
check
),
findsNothing
);
expect
(
rowWithIcons
(
Icons
.
add_alarm
,
Icons
.
check
),
findsOneWidget
);
});
testWidgets
(
'SegmentedButtons have correct semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
),
enabled:
false
),
],
selected:
const
<
int
>{
2
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{},
),
),
),
);
expect
(
semantics
,
hasSemantics
(
TestSemantics
.
root
(
children:
<
TestSemantics
>[
// First is an unselected, enabled button.
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isButton
,
SemanticsFlag
.
isEnabled
,
SemanticsFlag
.
hasEnabledState
,
SemanticsFlag
.
hasCheckedState
,
SemanticsFlag
.
isFocusable
,
SemanticsFlag
.
isInMutuallyExclusiveGroup
,
],
label:
'1'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
// Second is a selected, enabled button.
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isButton
,
SemanticsFlag
.
isEnabled
,
SemanticsFlag
.
hasEnabledState
,
SemanticsFlag
.
hasCheckedState
,
SemanticsFlag
.
isChecked
,
SemanticsFlag
.
isFocusable
,
SemanticsFlag
.
isInMutuallyExclusiveGroup
,
],
label:
'2'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
// Third is an unselected, disabled button.
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isButton
,
SemanticsFlag
.
hasEnabledState
,
SemanticsFlag
.
hasCheckedState
,
SemanticsFlag
.
isInMutuallyExclusiveGroup
,
],
label:
'3'
,
),
],
),
ignoreId:
true
,
ignoreRect:
true
,
ignoreTransform:
true
,
),
);
semantics
.
dispose
();
});
testWidgets
(
'Multi-select SegmentedButtons have correct semantics'
,
(
WidgetTester
tester
)
async
{
final
SemanticsTester
semantics
=
SemanticsTester
(
tester
);
await
tester
.
pumpWidget
(
Material
(
child:
boilerplate
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
),
enabled:
false
),
],
selected:
const
<
int
>{
1
,
3
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{},
multiSelectionEnabled:
true
,
),
),
),
);
expect
(
semantics
,
hasSemantics
(
TestSemantics
.
root
(
children:
<
TestSemantics
>[
// First is selected, enabled button.
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isButton
,
SemanticsFlag
.
isEnabled
,
SemanticsFlag
.
hasEnabledState
,
SemanticsFlag
.
hasCheckedState
,
SemanticsFlag
.
isChecked
,
SemanticsFlag
.
isFocusable
,
],
label:
'1'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
// Second is an unselected, enabled button.
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isButton
,
SemanticsFlag
.
isEnabled
,
SemanticsFlag
.
hasEnabledState
,
SemanticsFlag
.
hasCheckedState
,
SemanticsFlag
.
isFocusable
,
],
label:
'2'
,
actions:
<
SemanticsAction
>[
SemanticsAction
.
tap
,
],
),
// Third is a selected, disabled button.
TestSemantics
(
flags:
<
SemanticsFlag
>[
SemanticsFlag
.
isButton
,
SemanticsFlag
.
hasEnabledState
,
SemanticsFlag
.
isChecked
,
SemanticsFlag
.
hasCheckedState
,
],
label:
'3'
,
),
],
),
ignoreId:
true
,
ignoreRect:
true
,
ignoreTransform:
true
,
),
);
semantics
.
dispose
();
});
}
packages/flutter/test/material/segmented_button_theme_test.dart
0 → 100644
View file @
6ec2bd0a
// 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/foundation.dart'
;
import
'package:flutter/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
void
main
(
)
{
test
(
'SegmentedButtonThemeData copyWith, ==, hashCode basics'
,
()
{
expect
(
const
SegmentedButtonThemeData
(),
const
SegmentedButtonThemeData
().
copyWith
());
expect
(
const
SegmentedButtonThemeData
().
hashCode
,
const
SegmentedButtonThemeData
().
copyWith
().
hashCode
);
const
SegmentedButtonThemeData
custom
=
SegmentedButtonThemeData
(
style:
ButtonStyle
(
backgroundColor:
MaterialStatePropertyAll
<
Color
>(
Colors
.
green
)),
selectedIcon:
Icon
(
Icons
.
error
),
);
final
SegmentedButtonThemeData
copy
=
const
SegmentedButtonThemeData
().
copyWith
(
style:
custom
.
style
,
selectedIcon:
custom
.
selectedIcon
,
);
expect
(
copy
,
custom
);
});
testWidgets
(
'Default SegmentedButtonThemeData debugFillProperties'
,
(
WidgetTester
tester
)
async
{
final
DiagnosticPropertiesBuilder
builder
=
DiagnosticPropertiesBuilder
();
const
SegmentedButtonThemeData
().
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
(
'With no other configuration, defaults are used'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
Scaffold
(
body:
Center
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
),
enabled:
false
),
],
selected:
const
<
int
>{
2
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{
},
),
),
),
),
);
// Test first segment, should be enabled
{
final
Finder
text
=
find
.
text
(
'1'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
check
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
theme
.
colorScheme
.
primary
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
// Test second segment, should be enabled and selected
{
final
Finder
text
=
find
.
text
(
'2'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
check
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
color
,
theme
.
colorScheme
.
secondaryContainer
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
theme
.
colorScheme
.
onSecondaryContainer
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsOneWidget
);
}
// Test last segment, should be disabled
{
final
Finder
text
=
find
.
text
(
'3'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
check
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
theme
.
colorScheme
.
onSurface
.
withOpacity
(
0.38
));
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
});
testWidgets
(
'ThemeData.segmentedButtonTheme overrides defaults'
,
(
WidgetTester
tester
)
async
{
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
,
segmentedButtonTheme:
SegmentedButtonThemeData
(
style:
ButtonStyle
(
backgroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
blue
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
purple
;
}
return
null
;
}),
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
yellow
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
brown
;
}
else
{
return
Colors
.
cyan
;
}
}),
),
selectedIcon:
const
Icon
(
Icons
.
error
),
),
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
Scaffold
(
body:
Center
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
),
enabled:
false
),
],
selected:
const
<
int
>{
2
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{
},
),
),
),
),
);
// Test first segment, should be enabled
{
final
Finder
text
=
find
.
text
(
'1'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
error
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
cyan
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
// Test second segment, should be enabled and selected
{
final
Finder
text
=
find
.
text
(
'2'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
error
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
color
,
Colors
.
purple
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
brown
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsOneWidget
);
}
// Test last segment, should be disabled
{
final
Finder
text
=
find
.
text
(
'3'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
error
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
color
,
Colors
.
blue
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
yellow
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
});
testWidgets
(
'SegmentedButtonTheme overrides ThemeData and defaults'
,
(
WidgetTester
tester
)
async
{
final
SegmentedButtonThemeData
global
=
SegmentedButtonThemeData
(
style:
ButtonStyle
(
backgroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
blue
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
purple
;
}
return
null
;
}),
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
yellow
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
brown
;
}
else
{
return
Colors
.
cyan
;
}
}),
),
selectedIcon:
const
Icon
(
Icons
.
error
),
);
final
SegmentedButtonThemeData
segmentedTheme
=
SegmentedButtonThemeData
(
style:
ButtonStyle
(
backgroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
lightBlue
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
lightGreen
;
}
return
null
;
}),
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
lime
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
amber
;
}
else
{
return
Colors
.
deepPurple
;
}
}),
),
selectedIcon:
const
Icon
(
Icons
.
plus_one
),
);
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
,
segmentedButtonTheme:
global
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
SegmentedButtonTheme
(
data:
segmentedTheme
,
child:
Scaffold
(
body:
Center
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
),
enabled:
false
),
],
selected:
const
<
int
>{
2
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{
},
),
),
),
),
),
);
// Test first segment, should be enabled
{
final
Finder
text
=
find
.
text
(
'1'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
plus_one
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
deepPurple
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
// Test second segment, should be enabled and selected
{
final
Finder
text
=
find
.
text
(
'2'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
plus_one
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
lightGreen
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
amber
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsOneWidget
);
}
// Test last segment, should be disabled
{
final
Finder
text
=
find
.
text
(
'3'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
plus_one
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
lightBlue
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
lime
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
});
testWidgets
(
'Widget parameters overrides SegmentedTheme, ThemeData and defaults'
,
(
WidgetTester
tester
)
async
{
final
SegmentedButtonThemeData
global
=
SegmentedButtonThemeData
(
style:
ButtonStyle
(
backgroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
blue
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
purple
;
}
return
null
;
}),
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
yellow
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
brown
;
}
else
{
return
Colors
.
cyan
;
}
}),
),
selectedIcon:
const
Icon
(
Icons
.
error
),
);
final
SegmentedButtonThemeData
segmentedTheme
=
SegmentedButtonThemeData
(
style:
ButtonStyle
(
backgroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
lightBlue
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
lightGreen
;
}
return
null
;
}),
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
lime
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
amber
;
}
else
{
return
Colors
.
deepPurple
;
}
}),
),
selectedIcon:
const
Icon
(
Icons
.
plus_one
),
);
final
ThemeData
theme
=
ThemeData
(
useMaterial3:
true
,
segmentedButtonTheme:
global
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
theme:
theme
,
home:
SegmentedButtonTheme
(
data:
segmentedTheme
,
child:
Scaffold
(
body:
Center
(
child:
SegmentedButton
<
int
>(
segments:
const
<
ButtonSegment
<
int
>>[
ButtonSegment
<
int
>(
value:
1
,
label:
Text
(
'1'
)),
ButtonSegment
<
int
>(
value:
2
,
label:
Text
(
'2'
)),
ButtonSegment
<
int
>(
value:
3
,
label:
Text
(
'3'
),
enabled:
false
),
],
selected:
const
<
int
>{
2
},
onSelectionChanged:
(
Set
<
int
>
selected
)
{
},
style:
ButtonStyle
(
backgroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
black12
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
grey
;
}
return
null
;
}),
foregroundColor:
MaterialStateProperty
.
resolveWith
((
Set
<
MaterialState
>
states
)
{
if
(
states
.
contains
(
MaterialState
.
disabled
))
{
return
Colors
.
amberAccent
;
}
if
(
states
.
contains
(
MaterialState
.
selected
))
{
return
Colors
.
deepOrange
;
}
else
{
return
Colors
.
deepPurpleAccent
;
}
}),
),
selectedIcon:
const
Icon
(
Icons
.
alarm
),
),
),
),
),
),
);
// Test first segment, should be enabled
{
final
Finder
text
=
find
.
text
(
'1'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
alarm
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
transparent
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
deepPurpleAccent
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
// Test second segment, should be enabled and selected
{
final
Finder
text
=
find
.
text
(
'2'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
alarm
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
grey
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
deepOrange
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsOneWidget
);
}
// Test last segment, should be disabled
{
final
Finder
text
=
find
.
text
(
'3'
);
final
Finder
parent
=
find
.
ancestor
(
of:
text
,
matching:
find
.
byType
(
Material
)).
first
;
final
Finder
selectedIcon
=
find
.
descendant
(
of:
parent
,
matching:
find
.
byIcon
(
Icons
.
alarm
));
final
Material
material
=
tester
.
widget
<
Material
>(
parent
);
expect
(
material
.
animationDuration
,
const
Duration
(
milliseconds:
200
));
expect
(
material
.
borderRadius
,
null
);
expect
(
material
.
color
,
Colors
.
black12
);
expect
(
material
.
shape
,
const
RoundedRectangleBorder
());
expect
(
material
.
textStyle
!.
color
,
Colors
.
amberAccent
);
expect
(
material
.
textStyle
!.
fontFamily
,
'Roboto'
);
expect
(
material
.
textStyle
!.
fontSize
,
14
);
expect
(
material
.
textStyle
!.
fontWeight
,
FontWeight
.
w500
);
expect
(
selectedIcon
,
findsNothing
);
}
});
}
packages/flutter/test/material/theme_data_test.dart
View file @
6ec2bd0a
...
...
@@ -799,6 +799,7 @@ void main() {
popupMenuTheme:
const
PopupMenuThemeData
(
color:
Colors
.
black
),
progressIndicatorTheme:
const
ProgressIndicatorThemeData
(),
radioTheme:
const
RadioThemeData
(),
segmentedButtonTheme:
const
SegmentedButtonThemeData
(),
sliderTheme:
sliderTheme
,
snackBarTheme:
const
SnackBarThemeData
(
backgroundColor:
Colors
.
black
),
switchTheme:
const
SwitchThemeData
(),
...
...
@@ -917,6 +918,7 @@ void main() {
popupMenuTheme:
const
PopupMenuThemeData
(
color:
Colors
.
white
),
progressIndicatorTheme:
const
ProgressIndicatorThemeData
(),
radioTheme:
const
RadioThemeData
(),
segmentedButtonTheme:
const
SegmentedButtonThemeData
(),
sliderTheme:
otherSliderTheme
,
snackBarTheme:
const
SnackBarThemeData
(
backgroundColor:
Colors
.
white
),
switchTheme:
const
SwitchThemeData
(),
...
...
@@ -1263,6 +1265,7 @@ void main() {
'popupMenuTheme'
,
'progressIndicatorTheme'
,
'radioTheme'
,
'segmentedButtonTheme'
,
'sliderTheme'
,
'snackBarTheme'
,
'switchTheme'
,
...
...
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